387 lines
13 KiB
C++
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);
|
|
}
|
|
}
|