diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index 95aa3d09a..7bfdc315b 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -230,6 +230,29 @@ void GPU_SW::DispatchRenderCommand(RenderCommand rc, u32 num_vertices, const u32 case Primitive::Line: { + const u32 first_color = rc.color_for_first_vertex; + const bool shaded = rc.shading_enable; + + std::array vertices = {}; + u32 buffer_pos = 1; + + // first vertex + SWVertex* p0 = &vertices[0]; + SWVertex* p1 = &vertices[1]; + p0->SetPosition(VertexPosition{command_ptr[buffer_pos++]}); + p0->SetColorRGB24(first_color); + + // remaining vertices in line strip + for (u32 i = 1; i < num_vertices; i++) + { + p1->SetColorRGB24(shaded ? (command_ptr[buffer_pos++] & UINT32_C(0x00FFFFFF)) : first_color); + p1->SetPosition(VertexPosition{command_ptr[buffer_pos++]}); + + DrawLine(rc, p0, p1); + + // swap p0/p1 so that the last vertex is used as the first for the next line + std::swap(p0, p1); + } } break; @@ -239,6 +262,31 @@ void GPU_SW::DispatchRenderCommand(RenderCommand rc, u32 num_vertices, const u32 } } +enum : u32 +{ + COORD_FRAC_BITS = 32, + COLOR_FRAC_BITS = 12 +}; + +using FixedPointCoord = u64; + +constexpr FixedPointCoord IntToFixedCoord(s32 x) +{ + return (ZeroExtend64(static_cast(x)) << COORD_FRAC_BITS) | (ZeroExtend64(1u) << (COORD_FRAC_BITS - 1)); +} + +using FixedPointColor = u32; + +constexpr FixedPointColor IntToFixedColor(u8 r) +{ + return ZeroExtend32(r) << COLOR_FRAC_BITS | (1u << (COLOR_FRAC_BITS - 1)); +} + +constexpr u8 FixedColorToInt(FixedPointColor r) +{ + return Truncate8(r >> 12); +} + bool GPU_SW::IsClockwiseWinding(const SWVertex* v0, const SWVertex* v1, const SWVertex* v2) { const s32 abx = v1->x - v0->x; @@ -505,6 +553,95 @@ void GPU_SW::ShadePixel(RenderCommand rc, u32 x, u32 y, u8 color_r, u8 color_g, SetPixel(static_cast(x), static_cast(y), color.bits | m_GPUSTAT.GetMaskOR()); } + +constexpr FixedPointCoord GetLineCoordStep(s32 delta, s32 k) +{ + s64 delta_fp = static_cast(ZeroExtend64(static_cast(delta)) << 32); + if (delta_fp < 0) + delta_fp -= s64(k - 1); + if (delta_fp > 0) + delta_fp += s64(k - 1); + + return static_cast(delta_fp / k); +} + +constexpr s32 FixedToIntCoord(FixedPointCoord x) +{ + return static_cast(Truncate32(x >> COORD_FRAC_BITS)); +} + +constexpr FixedPointColor GetLineColorStep(s32 delta, s32 k) +{ + return static_cast(static_cast(delta) << COLOR_FRAC_BITS) / k; +} + +void GPU_SW::DrawLine(RenderCommand rc, const SWVertex* p0, const SWVertex* p1) +{ + // Algorithm based on Mednafen. + + const bool dither_enable = rc.IsDitheringEnabled() && m_GPUSTAT.dither_enable; + const bool shaded = rc.shading_enable; + + if (p0->x > p1->x) + std::swap(p0, p1); + + const s32 dx = p1->x - p0->x; + const s32 dy = p1->y - p0->y; + const s32 k = std::max(std::abs(dx), std::abs(dy)); + + FixedPointCoord step_x, step_y; + FixedPointColor step_r = 0, step_g = 0, step_b = 0; + if (k > 0) + { + step_x = GetLineCoordStep(dx, k); + step_y = GetLineCoordStep(dy, k); + + if (shaded) + { + step_r = GetLineColorStep(s32(ZeroExtend32(p1->color_r)) - s32(ZeroExtend32(p0->color_r)), k); + step_g = GetLineColorStep(s32(ZeroExtend32(p1->color_g)) - s32(ZeroExtend32(p0->color_g)), k); + step_b = GetLineColorStep(s32(ZeroExtend32(p1->color_b)) - s32(ZeroExtend32(p0->color_b)), k); + } + } + else + { + step_x = 0; + step_y = 0; + } + + FixedPointCoord current_x = IntToFixedCoord(p0->x); + FixedPointCoord current_y = IntToFixedCoord(p0->y); + FixedPointColor current_r = IntToFixedColor(p0->color_r); + FixedPointColor current_g = IntToFixedColor(p0->color_g); + FixedPointColor current_b = IntToFixedColor(p0->color_b); + + for (s32 i = 0; i <= k; i++) + { + const s32 x = m_drawing_offset.x + FixedToIntCoord(current_x); + const s32 y = m_drawing_offset.y + FixedToIntCoord(current_y); + + const u8 r = shaded ? FixedColorToInt(current_r) : p0->color_r; + const u8 g = shaded ? FixedColorToInt(current_g) : p0->color_g; + const u8 b = shaded ? FixedColorToInt(current_b) : p0->color_b; + + if (x >= static_cast(m_drawing_area.left) && x <= static_cast(m_drawing_area.right) && + y >= static_cast(m_drawing_area.top) && y <= static_cast(m_drawing_area.bottom)) + { + ShadePixel(rc, static_cast(x), static_cast(y), r, g, b, 0, 0, dither_enable); + } + + current_x += step_x; + current_y += step_y; + + if (shaded) + { + current_r += step_r; + current_g += step_g; + current_b += step_b; + } + } +} + std::unique_ptr GPU::CreateSoftwareRenderer() { return std::make_unique(); diff --git a/src/core/gpu_sw.h b/src/core/gpu_sw.h index da684eb56..8ee1a1f08 100644 --- a/src/core/gpu_sw.h +++ b/src/core/gpu_sw.h @@ -32,6 +32,15 @@ protected: s32 x, y; u8 color_r, color_g, color_b; u8 texcoord_x, texcoord_y; + + ALWAYS_INLINE void SetPosition(VertexPosition p) + { + x = p.x; + y = p.y; + } + + ALWAYS_INLINE void SetColorRGB24(u32 color) { std::tie(color_r, color_g, color_b) = UnpackColorRGB24(color); } + ALWAYS_INLINE void SetTexcoord(u16 value) { std::tie(texcoord_x, texcoord_y) = UnpackTexcoord(value); } }; void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override; @@ -60,6 +69,7 @@ protected: void DrawTriangle(RenderCommand rc, const SWVertex* v0, const SWVertex* v1, const SWVertex* v2); void DrawRectangle(RenderCommand rc, s32 origin_x, s32 origin_y, u32 width, u32 height, u8 r, u8 g, u8 b, u8 origin_texcoord_x, u8 origin_texcoord_y); + void DrawLine(RenderCommand rc, const SWVertex* p0, const SWVertex* p1); std::vector m_display_texture_buffer; std::unique_ptr m_display_texture;