IMX6U-Game/src/Core/Draw2D/DrawContext.cpp

387 lines
13 KiB
C++

#include "DrawContext.h"
#include "FrameBuffer.h"
#include "DepthBuffer.h"
#include "Rasterizer.h"
#include "TriangleRasterizer.h"
#include "Display.h"
#include <algorithm>
namespace Core
{
DrawContext::DrawContext(int32_t width, int32_t height)
{
frameBuffer = new Core::FrameBuffer(width, height);
depthBuffer = new Core::DepthBuffer(width, height);
rasterizer = new Rasterizer::Rasterizer(frameBuffer, depthBuffer);
triangleRasterizer = new Rasterizer::TriangleRasterizer(frameBuffer, depthBuffer);
}
DrawContext::~DrawContext()
{
delete triangleRasterizer;
delete rasterizer;
delete depthBuffer;
delete frameBuffer;
}
int32_t DrawContext::get_width() const
{
return frameBuffer->get_width();
}
int32_t DrawContext::get_height() const
{
return frameBuffer->get_height();
}
void DrawContext::clear(const RenderData::Color& color)
{
frameBuffer->clear(color);
depthBuffer->clear();
}
void DrawContext::clear_color(const RenderData::Color& color)
{
frameBuffer->clear(color);
}
void DrawContext::clear_depth()
{
depthBuffer->clear();
}
void DrawContext::draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color)
{
rasterizer->DrawLine(from, to, color);
}
void DrawContext::draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color)
{
triangleRasterizer->DrawTriangle2D(triangle, color);
}
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img)
{
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
}
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
{
draw_sprite_region(dst_x, dst_y, region);
}
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h)
{
draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false);
}
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
{
draw_sprite_region_ex(dst_x, dst_y, region, 1, false, false);
}
void DrawContext::draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
int32_t scale, bool flip_h, bool flip_v)
{
if (!region.atlas) return;
draw_sprite_ex(
dst_x,
dst_y,
*region.atlas,
region.x,
region.y,
region.width,
region.height,
scale,
flip_h,
flip_v);
}
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
int32_t scale, bool flip_h, bool flip_v)
{
if (scale < 1 || !img.pixels || src_w <= 0 || src_h <= 0) return;
if (src_x < 0 || src_y < 0 || src_x + src_w > img.width || src_y + src_h > img.height) return;
const int32_t img_w = img.width;
const int32_t screen_w = frameBuffer->get_width();
const int32_t screen_h = frameBuffer->get_height();
const int32_t draw_w = src_w * scale;
const int32_t draw_h = src_h * scale;
if (dst_x >= screen_w || dst_y >= screen_h || dst_x + draw_w <= 0 || dst_y + draw_h <= 0) return;
int32_t start_dx = 0;
int32_t start_dy = 0;
int32_t end_dx = draw_w;
int32_t end_dy = draw_h;
if (dst_x < 0) start_dx = -dst_x;
if (dst_y < 0) start_dy = -dst_y;
if (dst_x + end_dx > screen_w) end_dx = screen_w - dst_x;
if (dst_y + end_dy > screen_h) end_dy = screen_h - dst_y;
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
const uint16_t* src = static_cast<const uint16_t*>(img.pixels);
if (scale == 1)
{
for (int32_t sy = start_dy; sy < end_dy; ++sy)
{
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
const int32_t dst_y_abs = dst_y + sy;
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w;
for (int32_t sx = start_dx; sx < end_dx; ++sx)
{
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
const uint16_t pixel = src[read_y * img_w + read_x];
if (!RenderData::rgba5551_is_opaque(pixel)) continue;
dst_row[dst_x + sx] = RenderData::rgba5551_to_rgb565(pixel);
}
}
return;
}
for (int32_t dy_abs = start_dy; dy_abs < end_dy; ++dy_abs)
{
const int32_t sy = dy_abs / scale;
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
const int32_t dst_y_abs = dst_y + dy_abs;
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w;
for (int32_t dx_abs = start_dx; dx_abs < end_dx; ++dx_abs)
{
const int32_t sx = dx_abs / scale;
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
const uint16_t pixel = src[read_y * img_w + read_x];
if (!RenderData::rgba5551_is_opaque(pixel)) continue;
dst_row[dst_x + dx_abs] = RenderData::rgba5551_to_rgb565(pixel);
}
}
}
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y,
int32_t camera_x, int32_t camera_y)
{
draw_tilemap(tilemap,
screen_x,
screen_y,
frameBuffer->get_width() - screen_x,
frameBuffer->get_height() - screen_y,
camera_x,
camera_y);
}
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
int32_t screen_x, int32_t screen_y,
int32_t viewport_w, int32_t viewport_h,
int32_t camera_x, int32_t camera_y)
{
if (!tilemap.tiles || !tilemap.atlas || !tilemap.atlas->pixels) return;
if (tilemap.width <= 0 || tilemap.height <= 0) return;
if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0 || tilemap.atlas_columns <= 0) return;
if (viewport_w <= 0 || viewport_h <= 0) return;
const int32_t viewport_left = screen_x;
const int32_t viewport_top = screen_y;
const int32_t viewport_right = screen_x + viewport_w;
const int32_t viewport_bottom = screen_y + viewport_h;
int32_t start_tile_x = camera_x / tilemap.tile_w;
int32_t start_tile_y = camera_y / tilemap.tile_h;
int32_t offset_x = -(camera_x % tilemap.tile_w);
int32_t offset_y = -(camera_y % tilemap.tile_h);
if (camera_x < 0 && camera_x % tilemap.tile_w != 0)
{
--start_tile_x;
offset_x = -camera_x - (-start_tile_x * tilemap.tile_w);
}
if (camera_y < 0 && camera_y % tilemap.tile_h != 0)
{
--start_tile_y;
offset_y = -camera_y - (-start_tile_y * tilemap.tile_h);
}
const int32_t visible_cols = viewport_w / tilemap.tile_w + 2;
const int32_t visible_rows = viewport_h / tilemap.tile_h + 2;
for (int32_t row = 0; row < visible_rows; ++row)
{
const int32_t map_y = start_tile_y + row;
if (map_y < 0 || map_y >= tilemap.height) continue;
const int32_t dst_y = screen_y + offset_y + row * tilemap.tile_h;
for (int32_t col = 0; col < visible_cols; ++col)
{
const int32_t map_x = start_tile_x + col;
if (map_x < 0 || map_x >= tilemap.width) continue;
const uint16_t tile_id = tilemap.get_tile(map_x, map_y);
if (tile_id == RenderData::Tilemap::EmptyTile) continue;
const int32_t src_x = (tile_id % tilemap.atlas_columns) * tilemap.tile_w;
const int32_t src_y = (tile_id / tilemap.atlas_columns) * tilemap.tile_h;
const int32_t dst_x = screen_x + offset_x + col * tilemap.tile_w;
const int32_t tile_right = dst_x + tilemap.tile_w;
const int32_t tile_bottom = dst_y + tilemap.tile_h;
int32_t clipped_left = dst_x;
int32_t clipped_top = dst_y;
int32_t clipped_right = tile_right;
int32_t clipped_bottom = tile_bottom;
if (clipped_left < viewport_left) clipped_left = viewport_left;
if (clipped_top < viewport_top) clipped_top = viewport_top;
if (clipped_right > viewport_right) clipped_right = viewport_right;
if (clipped_bottom > viewport_bottom) clipped_bottom = viewport_bottom;
if (clipped_left >= clipped_right || clipped_top >= clipped_bottom) continue;
const int32_t clipped_src_x = src_x + (clipped_left - dst_x);
const int32_t clipped_src_y = src_y + (clipped_top - dst_y);
const int32_t clipped_w = clipped_right - clipped_left;
const int32_t clipped_h = clipped_bottom - clipped_top;
draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas,
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
1, false, false);
}
}
}
void DrawContext::fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color)
{
const uint32_t rgba = color.to_rgba();
const int32_t fb_w = frameBuffer->get_width();
const int32_t fb_h = frameBuffer->get_height();
int32_t x0 = std::max(0, x);
int32_t y0 = std::max(0, y);
int32_t x1 = std::min(fb_w, x + w);
int32_t y1 = std::min(fb_h, y + h);
if (x0 >= x1 || y0 >= y1) return;
const Core::FramePixel pixel = Core::rgba_to_frame_pixel(rgba);
Core::FramePixel* buf = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
for (int32_t row = y0; row < y1; ++row)
{
Core::FramePixel* dst = buf + row * fb_w + x0;
for (int32_t i = 0; i < x1 - x0; ++i)
{
dst[i] = pixel;
}
}
}
void DrawContext::draw_font_mask_region(const RenderData::BitmapFont& font,
int32_t src_x, int32_t src_y,
int32_t src_w, int32_t src_h,
int32_t dst_x, int32_t dst_y,
uint16_t pixel)
{
Core::FramePixel* dst = static_cast<Core::FramePixel*>(frameBuffer->get_buffer());
const int32_t fb_w = frameBuffer->get_width();
for (int32_t row = 0; row < src_h; ++row)
{
const int32_t atlas_row = src_y + row;
const int32_t dst_y_abs = dst_y + row;
Core::FramePixel* dst_row = dst + dst_y_abs * fb_w + dst_x;
for (int32_t col = 0; col < src_w; ++col)
{
const int32_t atlas_x = src_x + col;
const int32_t bit_index = atlas_row * font.atlas_width + atlas_x;
const uint8_t packed = font.mask_bits[bit_index >> 3];
const uint8_t bit_mask = static_cast<uint8_t>(0x80u >> (bit_index & 7));
if ((packed & bit_mask) == 0u) continue;
dst_row[col] = pixel;
}
}
}
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
const RenderData::Color& color, const char* text)
{
if (!font.mask_bits || !text) return;
const int32_t cw = font.char_w;
const int32_t ch = font.char_h;
const int32_t atlas_w = font.atlas_width;
const int32_t atlas_h = font.atlas_height;
const int32_t screen_w = frameBuffer->get_width();
const int32_t screen_h = frameBuffer->get_height();
if (cw <= 0 || ch <= 0 || font.columns <= 0 || atlas_w <= 0 || atlas_h <= 0) return;
const Core::FramePixel glyph_pixel = Core::rgba_to_frame_pixel(color.to_rgba());
int32_t cursor_x = x;
for (const char* p = text; *p; ++p)
{
const int32_t index = static_cast<int32_t>(*p) - font.first_char;
if (index < 0)
{
cursor_x += cw;
continue;
}
const int32_t col = index % font.columns;
const int32_t row = index / font.columns;
const int32_t src_x = col * cw;
const int32_t src_y = row * ch;
if (src_x < 0 || src_y < 0 || src_x + cw > atlas_w || src_y + ch > atlas_h)
{
cursor_x += cw;
continue;
}
if (cursor_x + cw <= 0 || cursor_x >= screen_w || y + ch <= 0 || y >= screen_h)
{
cursor_x += cw;
continue;
}
int32_t sy_start = 0, sy_end = ch;
int32_t sx_start = 0, sx_end = cw;
if (y < 0) sy_start = -y;
if (y + ch > screen_h) sy_end = screen_h - y;
if (cursor_x < 0) sx_start = -cursor_x;
if (cursor_x + cw > screen_w) sx_end = screen_w - cursor_x;
draw_font_mask_region(font,
src_x + sx_start,
src_y + sy_start,
sx_end - sx_start,
sy_end - sy_start,
cursor_x + sx_start,
y + sy_start,
glyph_pixel);
cursor_x += cw;
}
}
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text)
{
if (!text) return;
int32_t len = 0;
for (const char* p = text; *p; ++p) ++len;
fill_rect(x, y, len * font.char_w, font.char_h, bg_color);
draw_text(font, x, y, color, text);
}
void DrawContext::present(Platform::IDisplay* display)
{
display->present(frameBuffer);
}
}