diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7124c2de8..d7f886cd2 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -66,7 +66,6 @@ add_library(core
gpu.h
gpu_backend.cpp
gpu_backend.h
- gpu_commands.cpp
gpu_dump.cpp
gpu_dump.h
gpu_helpers.h
diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index b3e1f4c42..b7d345c62 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -41,7 +41,6 @@
-
diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters
index 2dd895aca..7acb63471 100644
--- a/src/core/core.vcxproj.filters
+++ b/src/core/core.vcxproj.filters
@@ -18,7 +18,6 @@
-
diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index ac44da6fc..8e048ac87 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -3,6 +3,7 @@
#include "gpu.h"
#include "core.h"
+#include "cpu_pgxp.h"
#include "dma.h"
#include "gpu_backend.h"
#include "gpu_dump.h"
@@ -19,6 +20,7 @@
#include "timing_event.h"
#include "video_shadergen.h"
#include "video_thread.h"
+#include "video_thread_commands.h"
#include "util/gpu_device.h"
#include "util/image.h"
@@ -30,6 +32,7 @@
#include "util/translation.h"
#include "common/align.h"
+#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/gsvector_formatter.h"
@@ -2183,6 +2186,1222 @@ bool GPU::DumpVRAMToFile(std::string path, u32 width, u32 height, u32 stride, co
return image.SaveToFile(path.c_str(), Image::DEFAULT_SAVE_QUALITY, error);
}
+#define CHECK_COMMAND_SIZE(num_words) \
+ if (m_fifo.GetSize() < num_words) \
+ { \
+ m_command_total_words = num_words; \
+ return false; \
+ }
+
+static u32 s_cpu_to_vram_dump_id = 1;
+static u32 s_vram_to_cpu_dump_id = 1;
+
+static constexpr u32 ReplaceZero(u32 value, u32 value_for_zero)
+{
+ return value == 0 ? value_for_zero : value;
+}
+
+void GPU::TryExecuteCommands()
+{
+ while (m_pending_command_ticks <= m_max_run_ahead && !m_fifo.IsEmpty())
+ {
+ switch (m_blitter_state)
+ {
+ case BlitterState::Idle:
+ {
+ const u32 command = FifoPeek(0) >> 24;
+ if ((this->*s_GP0_command_handler_table[command])())
+ continue;
+ else
+ return;
+ }
+
+ case BlitterState::WritingVRAM:
+ {
+ DebugAssert(m_blit_remaining_words > 0);
+ const u32 words_to_copy = std::min(m_blit_remaining_words, m_fifo.GetSize());
+ m_blit_buffer.reserve(m_blit_buffer.size() + words_to_copy);
+ for (u32 i = 0; i < words_to_copy; i++)
+ m_blit_buffer.push_back(FifoPop());
+ m_blit_remaining_words -= words_to_copy;
+
+ DEBUG_LOG("VRAM write burst of {} words, {} words remaining", words_to_copy, m_blit_remaining_words);
+ if (m_blit_remaining_words == 0)
+ FinishVRAMWrite();
+
+ continue;
+ }
+
+ case BlitterState::ReadingVRAM:
+ {
+ return;
+ }
+ break;
+
+ case BlitterState::DrawingPolyLine:
+ {
+ const u32 words_per_vertex = m_render_command.shading_enable ? 2 : 1;
+ u32 terminator_index =
+ m_render_command.shading_enable ? ((static_cast(m_polyline_buffer.size()) & 1u) ^ 1u) : 0u;
+ for (; terminator_index < m_fifo.GetSize(); terminator_index += words_per_vertex)
+ {
+ // polyline must have at least two vertices, and the terminator is (word & 0xf000f000) == 0x50005000.
+ // terminator is on the first word for the vertex
+ if ((FifoPeek(terminator_index) & UINT32_C(0xF000F000)) == UINT32_C(0x50005000))
+ break;
+ }
+
+ const bool found_terminator = (terminator_index < m_fifo.GetSize());
+ const u32 words_to_copy = std::min(terminator_index, m_fifo.GetSize());
+ if (words_to_copy > 0)
+ {
+ m_polyline_buffer.reserve(m_polyline_buffer.size() + words_to_copy);
+ for (u32 i = 0; i < words_to_copy; i++)
+ m_polyline_buffer.push_back(m_fifo.Pop());
+ }
+
+ DEBUG_LOG("Added {} words to polyline", words_to_copy);
+ if (found_terminator)
+ {
+ // drop terminator
+ m_fifo.RemoveOne();
+ DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
+ FinishPolyline();
+ m_polyline_buffer.clear();
+ EndCommand();
+ continue;
+ }
+ }
+ break;
+ }
+ }
+}
+
+void GPU::ExecuteCommands()
+{
+ const bool was_executing_from_event = std::exchange(m_executing_commands, true);
+
+ TryExecuteCommands();
+ UpdateDMARequest();
+ UpdateGPUIdle();
+
+ m_executing_commands = was_executing_from_event;
+ if (!was_executing_from_event)
+ UpdateCommandTickEvent();
+}
+
+void GPU::EndCommand()
+{
+ m_blitter_state = BlitterState::Idle;
+ m_command_total_words = 0;
+}
+
+GPU::GP0CommandHandlerTable GPU::GenerateGP0CommandHandlerTable()
+{
+ GP0CommandHandlerTable table = {};
+ for (u32 i = 0; i < static_cast(table.size()); i++)
+ table[i] = &GPU::HandleUnknownGP0Command;
+ table[0x00] = &GPU::HandleNOPCommand;
+ table[0x01] = &GPU::HandleClearCacheCommand;
+ table[0x02] = &GPU::HandleFillRectangleCommand;
+ table[0x03] = &GPU::HandleNOPCommand;
+ for (u32 i = 0x04; i <= 0x1E; i++)
+ table[i] = &GPU::HandleNOPCommand;
+ table[0x1F] = &GPU::HandleInterruptRequestCommand;
+ for (u32 i = 0x20; i <= 0x7F; i++)
+ {
+ const GPURenderCommand rc{i << 24};
+ switch (rc.primitive)
+ {
+ case GPUPrimitive::Polygon:
+ table[i] = &GPU::HandleRenderPolygonCommand;
+ break;
+ case GPUPrimitive::Line:
+ table[i] = rc.polyline ? &GPU::HandleRenderPolyLineCommand : &GPU::HandleRenderLineCommand;
+ break;
+ case GPUPrimitive::Rectangle:
+ table[i] = &GPU::HandleRenderRectangleCommand;
+ break;
+ default:
+ table[i] = &GPU::HandleUnknownGP0Command;
+ break;
+ }
+ }
+ table[0xE0] = &GPU::HandleNOPCommand;
+ table[0xE1] = &GPU::HandleSetDrawModeCommand;
+ table[0xE2] = &GPU::HandleSetTextureWindowCommand;
+ table[0xE3] = &GPU::HandleSetDrawingAreaTopLeftCommand;
+ table[0xE4] = &GPU::HandleSetDrawingAreaBottomRightCommand;
+ table[0xE5] = &GPU::HandleSetDrawingOffsetCommand;
+ table[0xE6] = &GPU::HandleSetMaskBitCommand;
+ for (u32 i = 0xE7; i <= 0xEF; i++)
+ table[i] = &GPU::HandleNOPCommand;
+ for (u32 i = 0x80; i <= 0x9F; i++)
+ table[i] = &GPU::HandleCopyRectangleVRAMToVRAMCommand;
+ for (u32 i = 0xA0; i <= 0xBF; i++)
+ table[i] = &GPU::HandleCopyRectangleCPUToVRAMCommand;
+ for (u32 i = 0xC0; i <= 0xDF; i++)
+ table[i] = &GPU::HandleCopyRectangleVRAMToCPUCommand;
+
+ table[0xFF] = &GPU::HandleNOPCommand;
+
+ return table;
+}
+
+bool GPU::HandleUnknownGP0Command()
+{
+ const u32 command = FifoPeek() >> 24;
+ ERROR_LOG("Unimplemented GP0 command 0x{:02X}", command);
+
+ SmallString dump;
+ for (u32 i = 0; i < m_fifo.GetSize(); i++)
+ dump.append_format("{}{:08X}", (i > 0) ? " " : "", FifoPeek(i));
+ ERROR_LOG("FIFO: {}", dump);
+
+ m_fifo.RemoveOne();
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleNOPCommand()
+{
+ m_fifo.RemoveOne();
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleClearCacheCommand()
+{
+ DEBUG_LOG("GP0 clear cache");
+ InvalidateCLUT();
+ GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand());
+ m_fifo.RemoveOne();
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleInterruptRequestCommand()
+{
+ DEBUG_LOG("GP0 interrupt request");
+
+ m_GPUSTAT.interrupt_request = true;
+ InterruptController::SetLineState(InterruptController::IRQ::GPU, true);
+
+ m_fifo.RemoveOne();
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetDrawModeCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+ DEBUG_LOG("Set draw mode {:08X}", param);
+ SetDrawMode(Truncate16(param));
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetTextureWindowCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+ SetTextureWindow(param);
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetDrawingAreaTopLeftCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+ const u32 left = param & DRAWING_AREA_COORD_MASK;
+ const u32 top = (param >> 10) & DRAWING_AREA_COORD_MASK;
+ DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
+ if (m_drawing_area.left != left || m_drawing_area.top != top)
+ {
+ m_drawing_area.left = left;
+ m_drawing_area.top = top;
+ m_drawing_area_changed = true;
+ SetClampedDrawingArea();
+ }
+
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetDrawingAreaBottomRightCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+
+ const u32 right = param & DRAWING_AREA_COORD_MASK;
+ const u32 bottom = (param >> 10) & DRAWING_AREA_COORD_MASK;
+ DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom);
+ if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
+ {
+ m_drawing_area.right = right;
+ m_drawing_area.bottom = bottom;
+ m_drawing_area_changed = true;
+ SetClampedDrawingArea();
+ }
+
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetDrawingOffsetCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+ const s32 x = SignExtendN<11, s32>(param & 0x7FFu);
+ const s32 y = SignExtendN<11, s32>((param >> 11) & 0x7FFu);
+ DEBUG_LOG("Set drawing offset ({}, {})", x, y);
+ if (m_drawing_offset.x != x || m_drawing_offset.y != y)
+ {
+ m_drawing_offset.x = x;
+ m_drawing_offset.y = y;
+ }
+
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleSetMaskBitCommand()
+{
+ const u32 param = FifoPop() & 0x00FFFFFFu;
+
+ constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
+ const u32 gpustat_bits = (param & 0x03) << 11;
+ m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
+ DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
+ BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
+
+ AddCommandTicks(1);
+ EndCommand();
+ return true;
+}
+
+void GPU::PrepareForDraw()
+{
+ if (m_drawing_area_changed)
+ {
+ m_drawing_area_changed = false;
+ GPUBackendSetDrawingAreaCommand* cmd = GPUBackend::NewSetDrawingAreaCommand();
+ cmd->new_area = m_drawing_area;
+ GPUBackend::PushCommand(cmd);
+ }
+}
+
+void GPU::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const
+{
+ cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
+ cmd->active_line_lsb = ConvertToBoolUnchecked(m_crtc_state.active_line_lsb);
+ cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
+ cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
+ cmd->texture_enable = rc.IsTexturingEnabled();
+ cmd->raw_texture_enable = rc.raw_texture_enable;
+ cmd->transparency_enable = rc.transparency_enable;
+ cmd->shading_enable = rc.shading_enable;
+ cmd->quad_polygon = rc.quad_polygon;
+ cmd->dither_enable = rc.IsDitheringEnabled() && m_draw_mode.mode_reg.dither_enable;
+
+ cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
+ cmd->palette.bits = m_draw_mode.palette_reg.bits;
+ cmd->window = m_draw_mode.texture_window;
+}
+
+ALWAYS_INLINE u32 GPU::GetPolyLineVertexCount() const
+{
+ return (static_cast(m_polyline_buffer.size()) + BoolToUInt32(m_render_command.shading_enable)) >>
+ BoolToUInt8(m_render_command.shading_enable);
+}
+
+ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
+ bool textured, bool semitransparent)
+{
+ // This will not produce the correct results for triangles which are partially outside the clip area.
+ // However, usually it'll undershoot not overshoot. If we wanted to make this more accurate, we'd need to intersect
+ // the edges with the clip rectangle.
+ // TODO: Coordinates are exclusive, so off by one here...
+ const GSVector2i clamp_min = GSVector2i::load(&m_clamped_drawing_area.x);
+ const GSVector2i clamp_max = GSVector2i::load(&m_clamped_drawing_area.z);
+ v1 = v1.sat_s32(clamp_min, clamp_max);
+ v2 = v2.sat_s32(clamp_min, clamp_max);
+ v3 = v3.sat_s32(clamp_min, clamp_max);
+
+ TickCount pixels = std::abs((v1.x * v2.y + v2.x * v3.y + v3.x * v1.y - v1.x * v3.y - v2.x * v1.y - v3.x * v2.y) / 2);
+ if (textured)
+ pixels += pixels;
+ if (semitransparent || m_GPUSTAT.check_mask_before_draw)
+ pixels += (pixels + 1) / 2;
+ if (m_GPUSTAT.SkipDrawingToActiveField())
+ pixels /= 2;
+
+ AddCommandTicks(pixels);
+}
+
+ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, bool textured, bool semitransparent)
+{
+ const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
+
+ u32 drawn_width = clamped_rect.width();
+ u32 drawn_height = clamped_rect.height();
+
+ u32 ticks_per_row = drawn_width;
+ if (textured)
+ {
+ switch (m_draw_mode.mode_reg.texture_mode)
+ {
+ case GPUTextureMode::Palette4Bit:
+ ticks_per_row += drawn_width;
+ break;
+
+ case GPUTextureMode::Palette8Bit:
+ {
+ // Texture cache reload every 2 pixels, reads in 8 bytes (assuming 4x2). Cache only reloads if the
+ // draw width is greater than 128, otherwise the cache hits between rows.
+ if (drawn_width > 128)
+ ticks_per_row += (drawn_width / 4) * 8;
+ else if ((drawn_width * drawn_height) > 2048)
+ ticks_per_row += ((drawn_width / 4) * (4 * (128 / drawn_width)));
+ else
+ ticks_per_row += drawn_width;
+ }
+ break;
+
+ case GPUTextureMode::Direct16Bit:
+ case GPUTextureMode::Reserved_Direct16Bit:
+ {
+ // Same as above, except with 2x2 blocks instead of 4x2.
+ if (drawn_width > 128)
+ ticks_per_row += (drawn_width / 2) * 8;
+ else if ((drawn_width * drawn_height) > 1024)
+ ticks_per_row += ((drawn_width / 4) * (8 * (128 / drawn_width)));
+ else
+ ticks_per_row += drawn_width;
+ }
+ break;
+
+ DefaultCaseIsUnreachable()
+ }
+ }
+
+ if (semitransparent || m_GPUSTAT.check_mask_before_draw)
+ ticks_per_row += (drawn_width + 1u) / 2u;
+ if (m_GPUSTAT.SkipDrawingToActiveField())
+ drawn_height = std::max(drawn_height / 2, 1u);
+
+ AddCommandTicks(ticks_per_row * drawn_height);
+}
+
+ALWAYS_INLINE_RELEASE void GPU::AddDrawLineTicks(const GSVector4i rect, bool shaded)
+{
+ const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
+
+ // Needed because we're not multiplying either dimension.
+ if (clamped_rect.rempty())
+ return;
+
+ const u32 drawn_width = clamped_rect.width();
+ u32 drawn_height = clamped_rect.height();
+
+ if (m_GPUSTAT.SkipDrawingToActiveField())
+ drawn_height = std::max(drawn_height / 2, 1u);
+
+ AddCommandTicks(std::max(drawn_width, drawn_height));
+}
+
+bool GPU::HandleRenderPolygonCommand()
+{
+ const GPURenderCommand rc{FifoPeek(0)};
+
+ // shaded vertices use the colour from the first word for the first vertex
+ const u32 words_per_vertex = 1 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.shading_enable);
+ const u32 num_vertices = rc.quad_polygon ? 4 : 3;
+ const u32 total_words = words_per_vertex * num_vertices + BoolToUInt32(!rc.shading_enable);
+ CHECK_COMMAND_SIZE(total_words);
+
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ // setup time
+ static constexpr u16 s_setup_time[2][2][2] = {{{46, 226}, {334, 496}}, {{82, 262}, {370, 532}}};
+ const TickCount setup_ticks = static_cast(ZeroExtend32(
+ s_setup_time[BoolToUInt8(rc.quad_polygon)][BoolToUInt8(rc.shading_enable)][BoolToUInt8(rc.texture_enable)]));
+ AddCommandTicks(setup_ticks);
+
+ TRACE_LOG("Render {} {} {} {} polygon ({} verts, {} words per vert), {} setup ticks",
+ rc.quad_polygon ? "four-point" : "three-point", rc.transparency_enable ? "semi-transparent" : "opaque",
+ rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome", num_vertices,
+ words_per_vertex, setup_ticks);
+
+ // set draw state up
+ // TODO: Get rid of SetTexturePalette() and just fill it as needed
+ if (rc.texture_enable)
+ {
+ const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
+ SetDrawMode((texpage_attribute & GPUDrawModeReg::POLYGON_TEXPAGE_MASK) |
+ (m_draw_mode.mode_reg.bits & ~GPUDrawModeReg::POLYGON_TEXPAGE_MASK));
+ SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
+ UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
+ }
+
+ m_render_command.bits = rc.bits;
+ m_fifo.RemoveOne();
+
+ PrepareForDraw();
+
+ if (g_settings.gpu_pgxp_enable)
+ {
+ GPUBackendDrawPrecisePolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPrecisePolygonCommand(num_vertices);
+ FillDrawCommand(cmd, rc);
+ cmd->num_vertices = Truncate16(num_vertices);
+
+ const u32 first_color = rc.color_for_first_vertex;
+ const bool shaded = rc.shading_enable;
+ const bool textured = rc.texture_enable;
+ bool valid_w = g_settings.gpu_pgxp_texture_correction;
+ for (u32 i = 0; i < num_vertices; i++)
+ {
+ GPUBackendDrawPrecisePolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
+ vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
+ const u64 maddr_and_pos = m_fifo.Pop();
+ const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
+ vert->native_x = m_drawing_offset.x + vp.x;
+ vert->native_y = m_drawing_offset.y + vp.y;
+ vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
+
+ valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
+ m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
+ }
+
+ cmd->valid_w = valid_w;
+ if (!valid_w)
+ {
+ if (g_settings.gpu_pgxp_disable_2d)
+ {
+ // NOTE: This reads uninitialized data, but it's okay, it doesn't get used.
+ for (u32 i = 0; i < num_vertices; i++)
+ {
+ GPUBackendDrawPrecisePolygonCommand::Vertex& v = cmd->vertices[i];
+ GSVector2::store(&v.x, GSVector2(GSVector2i::load(&v.native_x)));
+ v.w = 1.0f;
+ }
+ }
+ else
+ {
+ for (u32 i = 0; i < num_vertices; i++)
+ cmd->vertices[i].w = 1.0f;
+ }
+ }
+
+ // Cull polygons which are too large.
+ const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].native_x);
+ const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].native_x);
+ const GSVector2i v2 = GSVector2i::load(&cmd->vertices[2].native_x);
+ const GSVector2i min_pos_12 = v1.min_s32(v2);
+ const GSVector2i max_pos_12 = v1.max_s32(v2);
+ const GSVector4i draw_rect_012 =
+ GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ const bool first_tri_culled =
+ (draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT);
+ if (first_tri_culled)
+ {
+ DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].native_x, cmd->vertices[0].native_y,
+ cmd->vertices[1].native_x, cmd->vertices[1].native_y, cmd->vertices[2].native_x,
+ cmd->vertices[2].native_y);
+
+ if (!rc.quad_polygon)
+ {
+ EndCommand();
+ return true;
+ }
+ }
+ else
+ {
+ AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[0].native_x),
+ GSVector2i::load(&cmd->vertices[1].native_x),
+ GSVector2i::load(&cmd->vertices[2].native_x), rc.shading_enable, rc.texture_enable,
+ rc.transparency_enable);
+ }
+
+ // quads
+ if (rc.quad_polygon)
+ {
+ const GSVector2i v3 = GSVector2i::load(&cmd->vertices[3].native_x);
+ const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
+ .upl64(GSVector4i(max_pos_12.max_s32(v3)))
+ .add32(GSVector4i::cxpr(0, 0, 1, 1));
+
+ // Cull polygons which are too large.
+ const bool second_tri_culled =
+ (draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT);
+ if (second_tri_culled)
+ {
+ DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].native_x,
+ cmd->vertices[2].native_y, cmd->vertices[1].native_x, cmd->vertices[1].native_y,
+ cmd->vertices[3].native_x, cmd->vertices[3].native_y);
+
+ if (first_tri_culled)
+ {
+ EndCommand();
+ return true;
+ }
+
+ // Remove second part of quad.
+ cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
+ 3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
+ cmd->num_vertices = 3;
+ }
+ else
+ {
+ AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[2].native_x),
+ GSVector2i::load(&cmd->vertices[1].native_x),
+ GSVector2i::load(&cmd->vertices[3].native_x), rc.shading_enable, rc.texture_enable,
+ rc.transparency_enable);
+
+ // If first part was culled, move the second part to the first.
+ if (first_tri_culled)
+ {
+ std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
+ std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
+ cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
+ 3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
+ cmd->num_vertices = 3;
+ }
+ }
+ }
+
+ GPUBackend::PushCommand(cmd);
+ }
+ else
+ {
+ GPUBackendDrawPolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPolygonCommand(num_vertices);
+ FillDrawCommand(cmd, rc);
+ cmd->num_vertices = Truncate16(num_vertices);
+
+ const u32 first_color = rc.color_for_first_vertex;
+ const bool shaded = rc.shading_enable;
+ const bool textured = rc.texture_enable;
+ for (u32 i = 0; i < num_vertices; i++)
+ {
+ GPUBackendDrawPolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
+ vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
+ const u64 maddr_and_pos = m_fifo.Pop();
+ const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
+ vert->x = m_drawing_offset.x + vp.x;
+ vert->y = m_drawing_offset.y + vp.y;
+ vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
+ }
+
+ // Cull polygons which are too large.
+ const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
+ const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
+ const GSVector2i v2 = GSVector2i::load(&cmd->vertices[2].x);
+ const GSVector2i min_pos_12 = v1.min_s32(v2);
+ const GSVector2i max_pos_12 = v1.max_s32(v2);
+ const GSVector4i draw_rect_012 =
+ GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ const bool first_tri_culled =
+ (draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT);
+ if (first_tri_culled)
+ {
+ DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
+ cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
+
+ if (!rc.quad_polygon)
+ {
+ EndCommand();
+ return true;
+ }
+ }
+ else
+ {
+ AddDrawTriangleTicks(v0, v1, v2, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
+ }
+
+ // quads
+ if (rc.quad_polygon)
+ {
+ const GSVector2i v3 = GSVector2i::load(&cmd->vertices[3].x);
+ const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
+ .upl64(GSVector4i(max_pos_12.max_s32(v3)))
+ .add32(GSVector4i::cxpr(0, 0, 1, 1));
+
+ // Cull polygons which are too large.
+ const bool second_tri_culled =
+ (draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT);
+ if (second_tri_culled)
+ {
+ DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
+ cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[3].x, cmd->vertices[3].y);
+
+ if (first_tri_culled)
+ {
+ EndCommand();
+ return true;
+ }
+
+ // Remove second part of quad.
+ cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
+ 3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
+ cmd->num_vertices = 3;
+ }
+ else
+ {
+ AddDrawTriangleTicks(v2, v1, v3, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
+
+ // If first part was culled, move the second part to the first.
+ if (first_tri_culled)
+ {
+ std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPolygonCommand::Vertex));
+ std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPolygonCommand::Vertex));
+ cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
+ 3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
+ cmd->num_vertices = 3;
+ }
+ }
+ }
+
+ GPUBackend::PushCommand(cmd);
+ }
+
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleRenderRectangleCommand()
+{
+ const GPURenderCommand rc{FifoPeek(0)};
+ const u32 total_words =
+ 2 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.rectangle_size == GPUDrawRectangleSize::Variable);
+
+ CHECK_COMMAND_SIZE(total_words);
+
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ if (rc.texture_enable)
+ {
+ SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
+ UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
+ }
+
+ const TickCount setup_ticks = 16;
+ AddCommandTicks(setup_ticks);
+
+ TRACE_LOG("Render {} {} {} rectangle ({} words), {} setup ticks",
+ rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
+ rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
+
+ m_render_command.bits = rc.bits;
+ m_fifo.RemoveOne();
+
+ PrepareForDraw();
+ GPUBackendDrawRectangleCommand* cmd = GPUBackend::NewDrawRectangleCommand();
+ FillDrawCommand(cmd, rc);
+ cmd->color = rc.color_for_first_vertex;
+
+ const GPUVertexPosition vp{FifoPop()};
+ cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
+ cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
+
+ if (rc.texture_enable)
+ {
+ const u32 texcoord_and_palette = FifoPop();
+ cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
+ cmd->texcoord = Truncate16(texcoord_and_palette);
+ }
+ else
+ {
+ cmd->palette.bits = 0;
+ cmd->texcoord = 0;
+ }
+
+ switch (rc.rectangle_size)
+ {
+ case GPUDrawRectangleSize::R1x1:
+ cmd->width = 1;
+ cmd->height = 1;
+ break;
+ case GPUDrawRectangleSize::R8x8:
+ cmd->width = 8;
+ cmd->height = 8;
+ break;
+ case GPUDrawRectangleSize::R16x16:
+ cmd->width = 16;
+ cmd->height = 16;
+ break;
+ default:
+ {
+ const u32 width_and_height = FifoPop();
+ cmd->width = static_cast(width_and_height & VRAM_WIDTH_MASK);
+ cmd->height = static_cast((width_and_height >> 16) & VRAM_HEIGHT_MASK);
+ }
+ break;
+ }
+
+ const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
+ AddDrawRectangleTicks(rect, rc.texture_enable, rc.transparency_enable);
+
+ GPUBackend::PushCommand(cmd);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleRenderLineCommand()
+{
+ const GPURenderCommand rc{FifoPeek(0)};
+ const u32 total_words = rc.shading_enable ? 4 : 3;
+ CHECK_COMMAND_SIZE(total_words);
+
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
+ rc.shading_enable ? "shaded" : "monochrome", total_words);
+
+ m_render_command.bits = rc.bits;
+ m_fifo.RemoveOne();
+
+ PrepareForDraw();
+
+ if (g_settings.gpu_pgxp_enable)
+ {
+ GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand(2);
+ FillDrawCommand(cmd, rc);
+ cmd->palette.bits = 0;
+
+ bool valid_w = g_settings.gpu_pgxp_texture_correction;
+ for (u32 i = 0; i < 2; i++)
+ {
+ const u32 color = ((i != 0 && rc.shading_enable) ? FifoPop() : rc.bits) & UINT32_C(0x00FFFFFF);
+ const u64 maddr_and_pos = m_fifo.Pop();
+ const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
+ GPUBackendDrawPreciseLineCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
+ vert->native_x = m_drawing_offset.x + vp.x;
+ vert->native_y = m_drawing_offset.y + vp.y;
+ vert->color = color;
+
+ valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
+ m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
+ }
+ if (!(cmd->valid_w = valid_w))
+ {
+ for (u32 i = 0; i < 2; i++)
+ cmd->vertices[i].w = 1.0f;
+ }
+
+ const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].native_x);
+ const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].native_x);
+ const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
+ {
+ DEBUG_LOG("Culling too-large line: {} - {}", v0, v1);
+ EndCommand();
+ return true;
+ }
+
+ AddDrawLineTicks(rect, rc.shading_enable);
+ GPUBackend::PushCommand(cmd);
+ }
+ else
+ {
+ GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand(2);
+ FillDrawCommand(cmd, rc);
+ cmd->palette.bits = 0;
+
+ if (rc.shading_enable)
+ {
+ cmd->vertices[0].color = rc.color_for_first_vertex;
+ const GPUVertexPosition start_pos{FifoPop()};
+ cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
+ cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
+
+ cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
+ const GPUVertexPosition end_pos{FifoPop()};
+ cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
+ cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
+ }
+ else
+ {
+ cmd->vertices[0].color = rc.color_for_first_vertex;
+ cmd->vertices[1].color = rc.color_for_first_vertex;
+
+ const GPUVertexPosition start_pos{FifoPop()};
+ cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
+ cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
+
+ const GPUVertexPosition end_pos{FifoPop()};
+ cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
+ cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
+ }
+
+ const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
+ const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
+ const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
+ {
+ DEBUG_LOG("Culling too-large line: {} - {}", v0, v1);
+ EndCommand();
+ return true;
+ }
+
+ AddDrawLineTicks(rect, rc.shading_enable);
+ GPUBackend::PushCommand(cmd);
+ }
+
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleRenderPolyLineCommand()
+{
+ // always read the first two vertices, we test for the terminator after that
+ const GPURenderCommand rc{FifoPeek(0)};
+ const u32 min_words = rc.shading_enable ? 3 : 4;
+ CHECK_COMMAND_SIZE(min_words);
+
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ const TickCount setup_ticks = 16;
+ AddCommandTicks(setup_ticks);
+
+ TRACE_LOG("Render {} {} poly-line, {} setup ticks", rc.transparency_enable ? "semi-transparent" : "opaque",
+ rc.shading_enable ? "shaded" : "monochrome", setup_ticks);
+
+ m_render_command.bits = rc.bits;
+ m_fifo.RemoveOne();
+
+ const u32 words_to_pop = min_words - 1;
+ // m_blit_buffer.resize(words_to_pop);
+ // FifoPopRange(m_blit_buffer.data(), words_to_pop);
+ m_polyline_buffer.reserve(words_to_pop);
+ for (u32 i = 0; i < words_to_pop; i++)
+ m_polyline_buffer.push_back(m_fifo.Pop());
+
+ // polyline goes via a different path through the blit buffer
+ m_blitter_state = BlitterState::DrawingPolyLine;
+ m_command_total_words = 0;
+ return true;
+}
+
+void GPU::FinishPolyline()
+{
+ PrepareForDraw();
+
+ const u32 num_vertices = GetPolyLineVertexCount();
+ DebugAssert(num_vertices >= 2);
+
+ if (g_settings.gpu_pgxp_enable)
+ {
+ GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand((num_vertices - 1) * 2);
+ FillDrawCommand(cmd, m_render_command);
+ cmd->palette.bits = 0;
+
+ u32 buffer_pos = 0;
+ u32 out_vertex_count = 0;
+ const bool shaded = m_render_command.shading_enable;
+ bool valid_w = g_settings.gpu_pgxp_texture_correction;
+ GPUBackendDrawPreciseLineCommand::Vertex start, end;
+
+ const auto read_vertex = [this, &buffer_pos, &valid_w](GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT dest,
+ u32 color) {
+ const u64 maddr_and_pos = m_polyline_buffer[buffer_pos++];
+ const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
+ dest.native_x = m_drawing_offset.x + vp.x;
+ dest.native_y = m_drawing_offset.y + vp.y;
+ dest.color = color;
+ valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, dest.native_x, dest.native_y,
+ m_drawing_offset.x, m_drawing_offset.y, &dest.x, &dest.y, &dest.w);
+ };
+
+ read_vertex(start, m_render_command.color_for_first_vertex);
+
+ for (u32 i = 1; i < num_vertices; i++)
+ {
+ const u32 color =
+ (shaded ? Truncate32(m_polyline_buffer[buffer_pos++]) : m_render_command.bits) & UINT32_C(0x00FFFFFF);
+ read_vertex(end, color);
+
+ const GSVector2i start_pos = GSVector2i::load(&start.native_x);
+ const GSVector2i end_pos = GSVector2i::load(&end.native_x);
+ const GSVector4i rect =
+ GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
+ {
+ DEBUG_LOG("Culling too-large line: {} - {}", start_pos, end_pos);
+ }
+ else
+ {
+ AddDrawLineTicks(rect, m_render_command.shading_enable);
+
+ cmd->vertices[out_vertex_count++] = start;
+ cmd->vertices[out_vertex_count++] = end;
+ }
+
+ start = end;
+ }
+
+ if (out_vertex_count > 0)
+ {
+ DebugAssert(out_vertex_count <= cmd->num_vertices);
+ cmd->num_vertices = Truncate16(out_vertex_count);
+ GPUBackend::PushCommand(cmd);
+ }
+ }
+ else
+ {
+ GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand((num_vertices - 1) * 2);
+ FillDrawCommand(cmd, m_render_command);
+ cmd->palette.bits = 0;
+
+ u32 buffer_pos = 0;
+ const GPUVertexPosition start_vp{Truncate32(m_polyline_buffer[buffer_pos++])};
+ const GSVector2i draw_offset = GSVector2i::load(&m_drawing_offset.x);
+ GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
+ u32 start_color = m_render_command.color_for_first_vertex;
+
+ const bool shaded = m_render_command.shading_enable;
+ u32 out_vertex_count = 0;
+ for (u32 i = 1; i < num_vertices; i++)
+ {
+ const u32 end_color = shaded ? (Truncate32(m_polyline_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF))) :
+ m_render_command.color_for_first_vertex;
+ const GPUVertexPosition vp{Truncate32(m_polyline_buffer[buffer_pos++])};
+ const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
+
+ const GSVector4i rect =
+ GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
+ if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
+ {
+ DEBUG_LOG("Culling too-large line: {},{} - {},{}", start_pos.x, start_pos.y, end_pos.x, end_pos.y);
+ }
+ else
+ {
+ AddDrawLineTicks(rect, m_render_command.shading_enable);
+
+ GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
+ out_vertex_count += 2;
+
+ GSVector2i::store(&out_vertex[0].x, start_pos);
+ out_vertex[0].color = start_color;
+ GSVector2i::store(&out_vertex[1].x, end_pos);
+ out_vertex[1].color = end_color;
+ }
+
+ start_pos = end_pos;
+ start_color = end_color;
+ }
+
+ if (out_vertex_count > 0)
+ {
+ DebugAssert(out_vertex_count <= cmd->num_vertices);
+ cmd->num_vertices = Truncate16(out_vertex_count);
+ GPUBackend::PushCommand(cmd);
+ }
+ }
+}
+
+bool GPU::HandleFillRectangleCommand()
+{
+ CHECK_COMMAND_SIZE(3);
+
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ const u32 color = FifoPop() & 0x00FFFFFF;
+ const u32 dst_x = FifoPeek() & 0x3F0;
+ const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
+ const u32 width = ((FifoPeek() & VRAM_WIDTH_MASK) + 0xF) & ~0xF;
+ const u32 height = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
+
+ DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
+
+ if (width > 0 && height > 0)
+ {
+ GPUBackendFillVRAMCommand* cmd = GPUBackend::NewFillVRAMCommand();
+ cmd->x = static_cast(dst_x);
+ cmd->y = static_cast(dst_y);
+ cmd->width = static_cast(width);
+ cmd->height = static_cast(height);
+ cmd->color = color;
+ cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
+ cmd->active_line_lsb = m_crtc_state.active_line_lsb;
+ GPUBackend::PushCommand(cmd);
+ }
+
+ AddCommandTicks(46 + ((width / 8) + 9) * height);
+ EndCommand();
+ return true;
+}
+
+bool GPU::HandleCopyRectangleCPUToVRAMCommand()
+{
+ CHECK_COMMAND_SIZE(3);
+ m_fifo.RemoveOne();
+
+ const u32 coords = FifoPop();
+ const u32 size = FifoPop();
+
+ // Tenga Seiha does a bunch of completely-invalid VRAM writes on boot, then expects GPU idle to be set.
+ // It's unclear what actually happens, I need to write another test, but for now, just skip these uploads.
+ // Not setting GPU idle during the write command breaks Doom, so that's not an option.
+ if (size == 0xFFFFFFFFu) [[unlikely]]
+ {
+ ERROR_LOG("Ignoring likely-invalid VRAM write to ({},{})", (coords & VRAM_WIDTH_MASK),
+ ((coords >> 16) & VRAM_HEIGHT_MASK));
+ return true;
+ }
+
+ const u32 dst_x = coords & VRAM_WIDTH_MASK;
+ const u32 dst_y = (coords >> 16) & VRAM_HEIGHT_MASK;
+ const u32 copy_width = ReplaceZero(size & VRAM_WIDTH_MASK, 0x400);
+ const u32 copy_height = ReplaceZero((size >> 16) & VRAM_HEIGHT_MASK, 0x200);
+ const u32 num_pixels = copy_width * copy_height;
+ const u32 num_words = ((num_pixels + 1) / 2);
+
+ DEBUG_LOG("Copy rectangle from CPU to VRAM offset=({},{}), size=({},{})", dst_x, dst_y, copy_width, copy_height);
+
+ EndCommand();
+
+ m_blitter_state = BlitterState::WritingVRAM;
+ m_blit_buffer.reserve(num_words);
+ m_blit_remaining_words = num_words;
+ m_vram_transfer.x = Truncate16(dst_x);
+ m_vram_transfer.y = Truncate16(dst_y);
+ m_vram_transfer.width = Truncate16(copy_width);
+ m_vram_transfer.height = Truncate16(copy_height);
+ return true;
+}
+
+void GPU::FinishVRAMWrite()
+{
+ if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
+ SynchronizeCRTC();
+
+ if (m_blit_remaining_words == 0)
+ {
+ if (g_settings.gpu_dump_cpu_to_vram_copies)
+ {
+ DumpVRAMToFile(fmt::format("{}" FS_OSPATH_SEPARATOR_STR "cpu_to_vram_copy_{}.png", EmuFolders::DataRoot,
+ s_cpu_to_vram_dump_id++),
+ m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width,
+ m_blit_buffer.data(), true);
+ }
+
+ UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height,
+ m_blit_buffer.data(), m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
+ }
+ else
+ {
+ const u32 num_pixels = ZeroExtend32(m_vram_transfer.width) * ZeroExtend32(m_vram_transfer.height);
+ const u32 num_words = (num_pixels + 1) / 2;
+ const u32 transferred_words = num_words - m_blit_remaining_words;
+ const u32 transferred_pixels = transferred_words * 2;
+ const u32 transferred_full_rows = transferred_pixels / m_vram_transfer.width;
+ const u32 transferred_width_last_row = transferred_pixels % m_vram_transfer.width;
+
+ WARNING_LOG("Partial VRAM write - transfer finished with {} of {} words remaining ({} full rows, {} last row)",
+ m_blit_remaining_words, num_words, transferred_full_rows, transferred_width_last_row);
+
+ const u8* blit_ptr = reinterpret_cast(m_blit_buffer.data());
+ if (transferred_full_rows > 0)
+ {
+ UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, static_cast(transferred_full_rows),
+ blit_ptr, m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
+ blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16);
+ }
+ if (transferred_width_last_row > 0)
+ {
+ UpdateVRAM(m_vram_transfer.x, static_cast(m_vram_transfer.y + transferred_full_rows),
+ static_cast(transferred_width_last_row), 1, blit_ptr, m_GPUSTAT.set_mask_while_drawing,
+ m_GPUSTAT.check_mask_before_draw);
+ }
+ }
+
+ m_blit_buffer.clear();
+ m_vram_transfer = {};
+ m_blitter_state = BlitterState::Idle;
+}
+
+bool GPU::HandleCopyRectangleVRAMToCPUCommand()
+{
+ CHECK_COMMAND_SIZE(3);
+ m_fifo.RemoveOne();
+
+ m_vram_transfer.x = Truncate16(FifoPeek() & VRAM_WIDTH_MASK);
+ m_vram_transfer.y = Truncate16((FifoPop() >> 16) & VRAM_HEIGHT_MASK);
+ m_vram_transfer.width = ((Truncate16(FifoPeek()) - 1) & VRAM_WIDTH_MASK) + 1;
+ m_vram_transfer.height = ((Truncate16(FifoPop() >> 16) - 1) & VRAM_HEIGHT_MASK) + 1;
+
+ DEBUG_LOG("Copy rectangle from VRAM to CPU offset=({},{}), size=({},{})", m_vram_transfer.x, m_vram_transfer.y,
+ m_vram_transfer.width, m_vram_transfer.height);
+ DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
+
+ // ensure VRAM shadow is up to date
+ ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
+
+ if (g_settings.gpu_dump_vram_to_cpu_copies)
+ {
+ DumpVRAMToFile(fmt::format("{}" FS_OSPATH_SEPARATOR_STR "vram_to_cpu_copy_{}.png", EmuFolders::DataRoot,
+ s_vram_to_cpu_dump_id++),
+ m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * VRAM_WIDTH,
+ &g_vram[m_vram_transfer.y * VRAM_WIDTH + m_vram_transfer.x], true);
+ }
+
+ // switch to pixel-by-pixel read state
+ m_blitter_state = BlitterState::ReadingVRAM;
+ m_command_total_words = 0;
+
+ // toss the entire read in the recorded trace. we might want to change this to mirroring GPUREAD in the future..
+ if (m_gpu_dump) [[unlikely]]
+ m_gpu_dump->WriteDiscardVRAMRead(m_vram_transfer.width, m_vram_transfer.height);
+
+ return true;
+}
+
+bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
+{
+ CHECK_COMMAND_SIZE(4);
+ m_fifo.RemoveOne();
+
+ const u32 src_x = FifoPeek() & VRAM_WIDTH_MASK;
+ const u32 src_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
+ const u32 dst_x = FifoPeek() & VRAM_WIDTH_MASK;
+ const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
+ const u32 width = ReplaceZero(FifoPeek() & VRAM_WIDTH_MASK, 0x400);
+ const u32 height = ReplaceZero((FifoPop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
+
+ DEBUG_LOG("Copy rectangle from VRAM to VRAM src=({},{}), dst=({},{}), size=({},{})", src_x, src_y, dst_x, dst_y,
+ width, height);
+
+ // Some VRAM copies aren't going to do anything. Most games seem to send a 2x2 VRAM copy at the end of a frame.
+ const bool skip_copy =
+ width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
+ if (!skip_copy)
+ {
+ GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand();
+ cmd->src_x = static_cast(src_x);
+ cmd->src_y = static_cast(src_y);
+ cmd->dst_x = static_cast(dst_x);
+ cmd->dst_y = static_cast(dst_y);
+ cmd->width = static_cast(width);
+ cmd->height = static_cast(height);
+ cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
+ cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
+ GPUBackend::PushCommand(cmd);
+ }
+
+ AddCommandTicks(width * height * 2);
+ EndCommand();
+ return true;
+}
+
void GPU::DrawDebugStateWindow(float scale)
{
if (ImGui::CollapsingHeader("GPU", ImGuiTreeNodeFlags_DefaultOpen))
diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp
deleted file mode 100644
index 12e16894d..000000000
--- a/src/core/gpu_commands.cpp
+++ /dev/null
@@ -1,1235 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin
-// SPDX-License-Identifier: CC-BY-NC-ND-4.0
-
-#include "cpu_pgxp.h"
-#include "gpu.h"
-#include "gpu_backend.h"
-#include "gpu_dump.h"
-#include "gpu_helpers.h"
-#include "video_thread_commands.h"
-#include "interrupt_controller.h"
-#include "system.h"
-
-#include "common/assert.h"
-#include "common/gsvector_formatter.h"
-#include "common/log.h"
-#include "common/path.h"
-#include "common/string_util.h"
-
-LOG_CHANNEL(GPU);
-
-#define CHECK_COMMAND_SIZE(num_words) \
- if (m_fifo.GetSize() < num_words) \
- { \
- m_command_total_words = num_words; \
- return false; \
- }
-
-static u32 s_cpu_to_vram_dump_id = 1;
-static u32 s_vram_to_cpu_dump_id = 1;
-
-static constexpr u32 ReplaceZero(u32 value, u32 value_for_zero)
-{
- return value == 0 ? value_for_zero : value;
-}
-
-void GPU::TryExecuteCommands()
-{
- while (m_pending_command_ticks <= m_max_run_ahead && !m_fifo.IsEmpty())
- {
- switch (m_blitter_state)
- {
- case BlitterState::Idle:
- {
- const u32 command = FifoPeek(0) >> 24;
- if ((this->*s_GP0_command_handler_table[command])())
- continue;
- else
- return;
- }
-
- case BlitterState::WritingVRAM:
- {
- DebugAssert(m_blit_remaining_words > 0);
- const u32 words_to_copy = std::min(m_blit_remaining_words, m_fifo.GetSize());
- m_blit_buffer.reserve(m_blit_buffer.size() + words_to_copy);
- for (u32 i = 0; i < words_to_copy; i++)
- m_blit_buffer.push_back(FifoPop());
- m_blit_remaining_words -= words_to_copy;
-
- DEBUG_LOG("VRAM write burst of {} words, {} words remaining", words_to_copy, m_blit_remaining_words);
- if (m_blit_remaining_words == 0)
- FinishVRAMWrite();
-
- continue;
- }
-
- case BlitterState::ReadingVRAM:
- {
- return;
- }
- break;
-
- case BlitterState::DrawingPolyLine:
- {
- const u32 words_per_vertex = m_render_command.shading_enable ? 2 : 1;
- u32 terminator_index =
- m_render_command.shading_enable ? ((static_cast(m_polyline_buffer.size()) & 1u) ^ 1u) : 0u;
- for (; terminator_index < m_fifo.GetSize(); terminator_index += words_per_vertex)
- {
- // polyline must have at least two vertices, and the terminator is (word & 0xf000f000) == 0x50005000.
- // terminator is on the first word for the vertex
- if ((FifoPeek(terminator_index) & UINT32_C(0xF000F000)) == UINT32_C(0x50005000))
- break;
- }
-
- const bool found_terminator = (terminator_index < m_fifo.GetSize());
- const u32 words_to_copy = std::min(terminator_index, m_fifo.GetSize());
- if (words_to_copy > 0)
- {
- m_polyline_buffer.reserve(m_polyline_buffer.size() + words_to_copy);
- for (u32 i = 0; i < words_to_copy; i++)
- m_polyline_buffer.push_back(m_fifo.Pop());
- }
-
- DEBUG_LOG("Added {} words to polyline", words_to_copy);
- if (found_terminator)
- {
- // drop terminator
- m_fifo.RemoveOne();
- DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
- FinishPolyline();
- m_polyline_buffer.clear();
- EndCommand();
- continue;
- }
- }
- break;
- }
- }
-}
-
-void GPU::ExecuteCommands()
-{
- const bool was_executing_from_event = std::exchange(m_executing_commands, true);
-
- TryExecuteCommands();
- UpdateDMARequest();
- UpdateGPUIdle();
-
- m_executing_commands = was_executing_from_event;
- if (!was_executing_from_event)
- UpdateCommandTickEvent();
-}
-
-void GPU::EndCommand()
-{
- m_blitter_state = BlitterState::Idle;
- m_command_total_words = 0;
-}
-
-GPU::GP0CommandHandlerTable GPU::GenerateGP0CommandHandlerTable()
-{
- GP0CommandHandlerTable table = {};
- for (u32 i = 0; i < static_cast(table.size()); i++)
- table[i] = &GPU::HandleUnknownGP0Command;
- table[0x00] = &GPU::HandleNOPCommand;
- table[0x01] = &GPU::HandleClearCacheCommand;
- table[0x02] = &GPU::HandleFillRectangleCommand;
- table[0x03] = &GPU::HandleNOPCommand;
- for (u32 i = 0x04; i <= 0x1E; i++)
- table[i] = &GPU::HandleNOPCommand;
- table[0x1F] = &GPU::HandleInterruptRequestCommand;
- for (u32 i = 0x20; i <= 0x7F; i++)
- {
- const GPURenderCommand rc{i << 24};
- switch (rc.primitive)
- {
- case GPUPrimitive::Polygon:
- table[i] = &GPU::HandleRenderPolygonCommand;
- break;
- case GPUPrimitive::Line:
- table[i] = rc.polyline ? &GPU::HandleRenderPolyLineCommand : &GPU::HandleRenderLineCommand;
- break;
- case GPUPrimitive::Rectangle:
- table[i] = &GPU::HandleRenderRectangleCommand;
- break;
- default:
- table[i] = &GPU::HandleUnknownGP0Command;
- break;
- }
- }
- table[0xE0] = &GPU::HandleNOPCommand;
- table[0xE1] = &GPU::HandleSetDrawModeCommand;
- table[0xE2] = &GPU::HandleSetTextureWindowCommand;
- table[0xE3] = &GPU::HandleSetDrawingAreaTopLeftCommand;
- table[0xE4] = &GPU::HandleSetDrawingAreaBottomRightCommand;
- table[0xE5] = &GPU::HandleSetDrawingOffsetCommand;
- table[0xE6] = &GPU::HandleSetMaskBitCommand;
- for (u32 i = 0xE7; i <= 0xEF; i++)
- table[i] = &GPU::HandleNOPCommand;
- for (u32 i = 0x80; i <= 0x9F; i++)
- table[i] = &GPU::HandleCopyRectangleVRAMToVRAMCommand;
- for (u32 i = 0xA0; i <= 0xBF; i++)
- table[i] = &GPU::HandleCopyRectangleCPUToVRAMCommand;
- for (u32 i = 0xC0; i <= 0xDF; i++)
- table[i] = &GPU::HandleCopyRectangleVRAMToCPUCommand;
-
- table[0xFF] = &GPU::HandleNOPCommand;
-
- return table;
-}
-
-bool GPU::HandleUnknownGP0Command()
-{
- const u32 command = FifoPeek() >> 24;
- ERROR_LOG("Unimplemented GP0 command 0x{:02X}", command);
-
- SmallString dump;
- for (u32 i = 0; i < m_fifo.GetSize(); i++)
- dump.append_format("{}{:08X}", (i > 0) ? " " : "", FifoPeek(i));
- ERROR_LOG("FIFO: {}", dump);
-
- m_fifo.RemoveOne();
- EndCommand();
- return true;
-}
-
-bool GPU::HandleNOPCommand()
-{
- m_fifo.RemoveOne();
- EndCommand();
- return true;
-}
-
-bool GPU::HandleClearCacheCommand()
-{
- DEBUG_LOG("GP0 clear cache");
- InvalidateCLUT();
- GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand());
- m_fifo.RemoveOne();
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleInterruptRequestCommand()
-{
- DEBUG_LOG("GP0 interrupt request");
-
- m_GPUSTAT.interrupt_request = true;
- InterruptController::SetLineState(InterruptController::IRQ::GPU, true);
-
- m_fifo.RemoveOne();
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetDrawModeCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
- DEBUG_LOG("Set draw mode {:08X}", param);
- SetDrawMode(Truncate16(param));
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetTextureWindowCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
- SetTextureWindow(param);
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetDrawingAreaTopLeftCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
- const u32 left = param & DRAWING_AREA_COORD_MASK;
- const u32 top = (param >> 10) & DRAWING_AREA_COORD_MASK;
- DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
- if (m_drawing_area.left != left || m_drawing_area.top != top)
- {
- m_drawing_area.left = left;
- m_drawing_area.top = top;
- m_drawing_area_changed = true;
- SetClampedDrawingArea();
- }
-
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetDrawingAreaBottomRightCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
-
- const u32 right = param & DRAWING_AREA_COORD_MASK;
- const u32 bottom = (param >> 10) & DRAWING_AREA_COORD_MASK;
- DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom);
- if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
- {
- m_drawing_area.right = right;
- m_drawing_area.bottom = bottom;
- m_drawing_area_changed = true;
- SetClampedDrawingArea();
- }
-
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetDrawingOffsetCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
- const s32 x = SignExtendN<11, s32>(param & 0x7FFu);
- const s32 y = SignExtendN<11, s32>((param >> 11) & 0x7FFu);
- DEBUG_LOG("Set drawing offset ({}, {})", x, y);
- if (m_drawing_offset.x != x || m_drawing_offset.y != y)
- {
- m_drawing_offset.x = x;
- m_drawing_offset.y = y;
- }
-
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleSetMaskBitCommand()
-{
- const u32 param = FifoPop() & 0x00FFFFFFu;
-
- constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
- const u32 gpustat_bits = (param & 0x03) << 11;
- m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
- DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
- BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
-
- AddCommandTicks(1);
- EndCommand();
- return true;
-}
-
-void GPU::PrepareForDraw()
-{
- if (m_drawing_area_changed)
- {
- m_drawing_area_changed = false;
- GPUBackendSetDrawingAreaCommand* cmd = GPUBackend::NewSetDrawingAreaCommand();
- cmd->new_area = m_drawing_area;
- GPUBackend::PushCommand(cmd);
- }
-}
-
-void GPU::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const
-{
- cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
- cmd->active_line_lsb = ConvertToBoolUnchecked(m_crtc_state.active_line_lsb);
- cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
- cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
- cmd->texture_enable = rc.IsTexturingEnabled();
- cmd->raw_texture_enable = rc.raw_texture_enable;
- cmd->transparency_enable = rc.transparency_enable;
- cmd->shading_enable = rc.shading_enable;
- cmd->quad_polygon = rc.quad_polygon;
- cmd->dither_enable = rc.IsDitheringEnabled() && m_draw_mode.mode_reg.dither_enable;
-
- cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
- cmd->palette.bits = m_draw_mode.palette_reg.bits;
- cmd->window = m_draw_mode.texture_window;
-}
-
-ALWAYS_INLINE u32 GPU::GetPolyLineVertexCount() const
-{
- return (static_cast(m_polyline_buffer.size()) + BoolToUInt32(m_render_command.shading_enable)) >>
- BoolToUInt8(m_render_command.shading_enable);
-}
-
-ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
- bool textured, bool semitransparent)
-{
- // This will not produce the correct results for triangles which are partially outside the clip area.
- // However, usually it'll undershoot not overshoot. If we wanted to make this more accurate, we'd need to intersect
- // the edges with the clip rectangle.
- // TODO: Coordinates are exclusive, so off by one here...
- const GSVector2i clamp_min = GSVector2i::load(&m_clamped_drawing_area.x);
- const GSVector2i clamp_max = GSVector2i::load(&m_clamped_drawing_area.z);
- v1 = v1.sat_s32(clamp_min, clamp_max);
- v2 = v2.sat_s32(clamp_min, clamp_max);
- v3 = v3.sat_s32(clamp_min, clamp_max);
-
- TickCount pixels = std::abs((v1.x * v2.y + v2.x * v3.y + v3.x * v1.y - v1.x * v3.y - v2.x * v1.y - v3.x * v2.y) / 2);
- if (textured)
- pixels += pixels;
- if (semitransparent || m_GPUSTAT.check_mask_before_draw)
- pixels += (pixels + 1) / 2;
- if (m_GPUSTAT.SkipDrawingToActiveField())
- pixels /= 2;
-
- AddCommandTicks(pixels);
-}
-
-ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, bool textured, bool semitransparent)
-{
- const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
-
- u32 drawn_width = clamped_rect.width();
- u32 drawn_height = clamped_rect.height();
-
- u32 ticks_per_row = drawn_width;
- if (textured)
- {
- switch (m_draw_mode.mode_reg.texture_mode)
- {
- case GPUTextureMode::Palette4Bit:
- ticks_per_row += drawn_width;
- break;
-
- case GPUTextureMode::Palette8Bit:
- {
- // Texture cache reload every 2 pixels, reads in 8 bytes (assuming 4x2). Cache only reloads if the
- // draw width is greater than 128, otherwise the cache hits between rows.
- if (drawn_width > 128)
- ticks_per_row += (drawn_width / 4) * 8;
- else if ((drawn_width * drawn_height) > 2048)
- ticks_per_row += ((drawn_width / 4) * (4 * (128 / drawn_width)));
- else
- ticks_per_row += drawn_width;
- }
- break;
-
- case GPUTextureMode::Direct16Bit:
- case GPUTextureMode::Reserved_Direct16Bit:
- {
- // Same as above, except with 2x2 blocks instead of 4x2.
- if (drawn_width > 128)
- ticks_per_row += (drawn_width / 2) * 8;
- else if ((drawn_width * drawn_height) > 1024)
- ticks_per_row += ((drawn_width / 4) * (8 * (128 / drawn_width)));
- else
- ticks_per_row += drawn_width;
- }
- break;
-
- DefaultCaseIsUnreachable()
- }
- }
-
- if (semitransparent || m_GPUSTAT.check_mask_before_draw)
- ticks_per_row += (drawn_width + 1u) / 2u;
- if (m_GPUSTAT.SkipDrawingToActiveField())
- drawn_height = std::max(drawn_height / 2, 1u);
-
- AddCommandTicks(ticks_per_row * drawn_height);
-}
-
-ALWAYS_INLINE_RELEASE void GPU::AddDrawLineTicks(const GSVector4i rect, bool shaded)
-{
- const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
-
- // Needed because we're not multiplying either dimension.
- if (clamped_rect.rempty())
- return;
-
- const u32 drawn_width = clamped_rect.width();
- u32 drawn_height = clamped_rect.height();
-
- if (m_GPUSTAT.SkipDrawingToActiveField())
- drawn_height = std::max(drawn_height / 2, 1u);
-
- AddCommandTicks(std::max(drawn_width, drawn_height));
-}
-
-bool GPU::HandleRenderPolygonCommand()
-{
- const GPURenderCommand rc{FifoPeek(0)};
-
- // shaded vertices use the colour from the first word for the first vertex
- const u32 words_per_vertex = 1 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.shading_enable);
- const u32 num_vertices = rc.quad_polygon ? 4 : 3;
- const u32 total_words = words_per_vertex * num_vertices + BoolToUInt32(!rc.shading_enable);
- CHECK_COMMAND_SIZE(total_words);
-
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- // setup time
- static constexpr u16 s_setup_time[2][2][2] = {{{46, 226}, {334, 496}}, {{82, 262}, {370, 532}}};
- const TickCount setup_ticks = static_cast(ZeroExtend32(
- s_setup_time[BoolToUInt8(rc.quad_polygon)][BoolToUInt8(rc.shading_enable)][BoolToUInt8(rc.texture_enable)]));
- AddCommandTicks(setup_ticks);
-
- TRACE_LOG("Render {} {} {} {} polygon ({} verts, {} words per vert), {} setup ticks",
- rc.quad_polygon ? "four-point" : "three-point", rc.transparency_enable ? "semi-transparent" : "opaque",
- rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome", num_vertices,
- words_per_vertex, setup_ticks);
-
- // set draw state up
- // TODO: Get rid of SetTexturePalette() and just fill it as needed
- if (rc.texture_enable)
- {
- const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
- SetDrawMode((texpage_attribute & GPUDrawModeReg::POLYGON_TEXPAGE_MASK) |
- (m_draw_mode.mode_reg.bits & ~GPUDrawModeReg::POLYGON_TEXPAGE_MASK));
- SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
- UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
- }
-
- m_render_command.bits = rc.bits;
- m_fifo.RemoveOne();
-
- PrepareForDraw();
-
- if (g_settings.gpu_pgxp_enable)
- {
- GPUBackendDrawPrecisePolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPrecisePolygonCommand(num_vertices);
- FillDrawCommand(cmd, rc);
- cmd->num_vertices = Truncate16(num_vertices);
-
- const u32 first_color = rc.color_for_first_vertex;
- const bool shaded = rc.shading_enable;
- const bool textured = rc.texture_enable;
- bool valid_w = g_settings.gpu_pgxp_texture_correction;
- for (u32 i = 0; i < num_vertices; i++)
- {
- GPUBackendDrawPrecisePolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
- vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
- const u64 maddr_and_pos = m_fifo.Pop();
- const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
- vert->native_x = m_drawing_offset.x + vp.x;
- vert->native_y = m_drawing_offset.y + vp.y;
- vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
-
- valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
- m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
- }
-
- cmd->valid_w = valid_w;
- if (!valid_w)
- {
- if (g_settings.gpu_pgxp_disable_2d)
- {
- // NOTE: This reads uninitialized data, but it's okay, it doesn't get used.
- for (u32 i = 0; i < num_vertices; i++)
- {
- GPUBackendDrawPrecisePolygonCommand::Vertex& v = cmd->vertices[i];
- GSVector2::store(&v.x, GSVector2(GSVector2i::load(&v.native_x)));
- v.w = 1.0f;
- }
- }
- else
- {
- for (u32 i = 0; i < num_vertices; i++)
- cmd->vertices[i].w = 1.0f;
- }
- }
-
- // Cull polygons which are too large.
- const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].native_x);
- const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].native_x);
- const GSVector2i v2 = GSVector2i::load(&cmd->vertices[2].native_x);
- const GSVector2i min_pos_12 = v1.min_s32(v2);
- const GSVector2i max_pos_12 = v1.max_s32(v2);
- const GSVector4i draw_rect_012 =
- GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- const bool first_tri_culled =
- (draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT);
- if (first_tri_culled)
- {
- DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].native_x, cmd->vertices[0].native_y,
- cmd->vertices[1].native_x, cmd->vertices[1].native_y, cmd->vertices[2].native_x,
- cmd->vertices[2].native_y);
-
- if (!rc.quad_polygon)
- {
- EndCommand();
- return true;
- }
- }
- else
- {
- AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[0].native_x),
- GSVector2i::load(&cmd->vertices[1].native_x),
- GSVector2i::load(&cmd->vertices[2].native_x), rc.shading_enable, rc.texture_enable,
- rc.transparency_enable);
- }
-
- // quads
- if (rc.quad_polygon)
- {
- const GSVector2i v3 = GSVector2i::load(&cmd->vertices[3].native_x);
- const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
- .upl64(GSVector4i(max_pos_12.max_s32(v3)))
- .add32(GSVector4i::cxpr(0, 0, 1, 1));
-
- // Cull polygons which are too large.
- const bool second_tri_culled =
- (draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT);
- if (second_tri_culled)
- {
- DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].native_x,
- cmd->vertices[2].native_y, cmd->vertices[1].native_x, cmd->vertices[1].native_y,
- cmd->vertices[3].native_x, cmd->vertices[3].native_y);
-
- if (first_tri_culled)
- {
- EndCommand();
- return true;
- }
-
- // Remove second part of quad.
- cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
- 3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
- cmd->num_vertices = 3;
- }
- else
- {
- AddDrawTriangleTicks(GSVector2i::load(&cmd->vertices[2].native_x),
- GSVector2i::load(&cmd->vertices[1].native_x),
- GSVector2i::load(&cmd->vertices[3].native_x), rc.shading_enable, rc.texture_enable,
- rc.transparency_enable);
-
- // If first part was culled, move the second part to the first.
- if (first_tri_culled)
- {
- std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
- std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
- cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
- 3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
- cmd->num_vertices = 3;
- }
- }
- }
-
- GPUBackend::PushCommand(cmd);
- }
- else
- {
- GPUBackendDrawPolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPolygonCommand(num_vertices);
- FillDrawCommand(cmd, rc);
- cmd->num_vertices = Truncate16(num_vertices);
-
- const u32 first_color = rc.color_for_first_vertex;
- const bool shaded = rc.shading_enable;
- const bool textured = rc.texture_enable;
- for (u32 i = 0; i < num_vertices; i++)
- {
- GPUBackendDrawPolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
- vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
- const u64 maddr_and_pos = m_fifo.Pop();
- const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
- vert->x = m_drawing_offset.x + vp.x;
- vert->y = m_drawing_offset.y + vp.y;
- vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
- }
-
- // Cull polygons which are too large.
- const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
- const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
- const GSVector2i v2 = GSVector2i::load(&cmd->vertices[2].x);
- const GSVector2i min_pos_12 = v1.min_s32(v2);
- const GSVector2i max_pos_12 = v1.max_s32(v2);
- const GSVector4i draw_rect_012 =
- GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- const bool first_tri_culled =
- (draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT);
- if (first_tri_culled)
- {
- DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
- cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
-
- if (!rc.quad_polygon)
- {
- EndCommand();
- return true;
- }
- }
- else
- {
- AddDrawTriangleTicks(v0, v1, v2, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
- }
-
- // quads
- if (rc.quad_polygon)
- {
- const GSVector2i v3 = GSVector2i::load(&cmd->vertices[3].x);
- const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
- .upl64(GSVector4i(max_pos_12.max_s32(v3)))
- .add32(GSVector4i::cxpr(0, 0, 1, 1));
-
- // Cull polygons which are too large.
- const bool second_tri_culled =
- (draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT);
- if (second_tri_culled)
- {
- DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
- cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[3].x, cmd->vertices[3].y);
-
- if (first_tri_culled)
- {
- EndCommand();
- return true;
- }
-
- // Remove second part of quad.
- cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
- 3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
- cmd->num_vertices = 3;
- }
- else
- {
- AddDrawTriangleTicks(v2, v1, v3, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
-
- // If first part was culled, move the second part to the first.
- if (first_tri_culled)
- {
- std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPolygonCommand::Vertex));
- std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPolygonCommand::Vertex));
- cmd->size = VideoThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
- 3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
- cmd->num_vertices = 3;
- }
- }
- }
-
- GPUBackend::PushCommand(cmd);
- }
-
- EndCommand();
- return true;
-}
-
-bool GPU::HandleRenderRectangleCommand()
-{
- const GPURenderCommand rc{FifoPeek(0)};
- const u32 total_words =
- 2 + BoolToUInt32(rc.texture_enable) + BoolToUInt32(rc.rectangle_size == GPUDrawRectangleSize::Variable);
-
- CHECK_COMMAND_SIZE(total_words);
-
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- if (rc.texture_enable)
- {
- SetTexturePalette(Truncate16(FifoPeek(2) >> 16));
- UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
- }
-
- const TickCount setup_ticks = 16;
- AddCommandTicks(setup_ticks);
-
- TRACE_LOG("Render {} {} {} rectangle ({} words), {} setup ticks",
- rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
- rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
-
- m_render_command.bits = rc.bits;
- m_fifo.RemoveOne();
-
- PrepareForDraw();
- GPUBackendDrawRectangleCommand* cmd = GPUBackend::NewDrawRectangleCommand();
- FillDrawCommand(cmd, rc);
- cmd->color = rc.color_for_first_vertex;
-
- const GPUVertexPosition vp{FifoPop()};
- cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
- cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
-
- if (rc.texture_enable)
- {
- const u32 texcoord_and_palette = FifoPop();
- cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
- cmd->texcoord = Truncate16(texcoord_and_palette);
- }
- else
- {
- cmd->palette.bits = 0;
- cmd->texcoord = 0;
- }
-
- switch (rc.rectangle_size)
- {
- case GPUDrawRectangleSize::R1x1:
- cmd->width = 1;
- cmd->height = 1;
- break;
- case GPUDrawRectangleSize::R8x8:
- cmd->width = 8;
- cmd->height = 8;
- break;
- case GPUDrawRectangleSize::R16x16:
- cmd->width = 16;
- cmd->height = 16;
- break;
- default:
- {
- const u32 width_and_height = FifoPop();
- cmd->width = static_cast(width_and_height & VRAM_WIDTH_MASK);
- cmd->height = static_cast((width_and_height >> 16) & VRAM_HEIGHT_MASK);
- }
- break;
- }
-
- const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
- AddDrawRectangleTicks(rect, rc.texture_enable, rc.transparency_enable);
-
- GPUBackend::PushCommand(cmd);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleRenderLineCommand()
-{
- const GPURenderCommand rc{FifoPeek(0)};
- const u32 total_words = rc.shading_enable ? 4 : 3;
- CHECK_COMMAND_SIZE(total_words);
-
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
- rc.shading_enable ? "shaded" : "monochrome", total_words);
-
- m_render_command.bits = rc.bits;
- m_fifo.RemoveOne();
-
- PrepareForDraw();
-
- if (g_settings.gpu_pgxp_enable)
- {
- GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand(2);
- FillDrawCommand(cmd, rc);
- cmd->palette.bits = 0;
-
- bool valid_w = g_settings.gpu_pgxp_texture_correction;
- for (u32 i = 0; i < 2; i++)
- {
- const u32 color = ((i != 0 && rc.shading_enable) ? FifoPop() : rc.bits) & UINT32_C(0x00FFFFFF);
- const u64 maddr_and_pos = m_fifo.Pop();
- const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
- GPUBackendDrawPreciseLineCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
- vert->native_x = m_drawing_offset.x + vp.x;
- vert->native_y = m_drawing_offset.y + vp.y;
- vert->color = color;
-
- valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
- m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
- }
- if (!(cmd->valid_w = valid_w))
- {
- for (u32 i = 0; i < 2; i++)
- cmd->vertices[i].w = 1.0f;
- }
-
- const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].native_x);
- const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].native_x);
- const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
- {
- DEBUG_LOG("Culling too-large line: {} - {}", v0, v1);
- EndCommand();
- return true;
- }
-
- AddDrawLineTicks(rect, rc.shading_enable);
- GPUBackend::PushCommand(cmd);
- }
- else
- {
- GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand(2);
- FillDrawCommand(cmd, rc);
- cmd->palette.bits = 0;
-
- if (rc.shading_enable)
- {
- cmd->vertices[0].color = rc.color_for_first_vertex;
- const GPUVertexPosition start_pos{FifoPop()};
- cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
- cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
-
- cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
- const GPUVertexPosition end_pos{FifoPop()};
- cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
- cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
- }
- else
- {
- cmd->vertices[0].color = rc.color_for_first_vertex;
- cmd->vertices[1].color = rc.color_for_first_vertex;
-
- const GPUVertexPosition start_pos{FifoPop()};
- cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
- cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
-
- const GPUVertexPosition end_pos{FifoPop()};
- cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
- cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
- }
-
- const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x);
- const GSVector2i v1 = GSVector2i::load(&cmd->vertices[1].x);
- const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
- {
- DEBUG_LOG("Culling too-large line: {} - {}", v0, v1);
- EndCommand();
- return true;
- }
-
- AddDrawLineTicks(rect, rc.shading_enable);
- GPUBackend::PushCommand(cmd);
- }
-
- EndCommand();
- return true;
-}
-
-bool GPU::HandleRenderPolyLineCommand()
-{
- // always read the first two vertices, we test for the terminator after that
- const GPURenderCommand rc{FifoPeek(0)};
- const u32 min_words = rc.shading_enable ? 3 : 4;
- CHECK_COMMAND_SIZE(min_words);
-
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- const TickCount setup_ticks = 16;
- AddCommandTicks(setup_ticks);
-
- TRACE_LOG("Render {} {} poly-line, {} setup ticks", rc.transparency_enable ? "semi-transparent" : "opaque",
- rc.shading_enable ? "shaded" : "monochrome", setup_ticks);
-
- m_render_command.bits = rc.bits;
- m_fifo.RemoveOne();
-
- const u32 words_to_pop = min_words - 1;
- // m_blit_buffer.resize(words_to_pop);
- // FifoPopRange(m_blit_buffer.data(), words_to_pop);
- m_polyline_buffer.reserve(words_to_pop);
- for (u32 i = 0; i < words_to_pop; i++)
- m_polyline_buffer.push_back(m_fifo.Pop());
-
- // polyline goes via a different path through the blit buffer
- m_blitter_state = BlitterState::DrawingPolyLine;
- m_command_total_words = 0;
- return true;
-}
-
-void GPU::FinishPolyline()
-{
- PrepareForDraw();
-
- const u32 num_vertices = GetPolyLineVertexCount();
- DebugAssert(num_vertices >= 2);
-
- if (g_settings.gpu_pgxp_enable)
- {
- GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand((num_vertices - 1) * 2);
- FillDrawCommand(cmd, m_render_command);
- cmd->palette.bits = 0;
-
- u32 buffer_pos = 0;
- u32 out_vertex_count = 0;
- const bool shaded = m_render_command.shading_enable;
- bool valid_w = g_settings.gpu_pgxp_texture_correction;
- GPUBackendDrawPreciseLineCommand::Vertex start, end;
-
- const auto read_vertex = [this, &buffer_pos, &valid_w](GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT dest,
- u32 color) {
- const u64 maddr_and_pos = m_polyline_buffer[buffer_pos++];
- const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
- dest.native_x = m_drawing_offset.x + vp.x;
- dest.native_y = m_drawing_offset.y + vp.y;
- dest.color = color;
- valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, dest.native_x, dest.native_y,
- m_drawing_offset.x, m_drawing_offset.y, &dest.x, &dest.y, &dest.w);
- };
-
- read_vertex(start, m_render_command.color_for_first_vertex);
-
- for (u32 i = 1; i < num_vertices; i++)
- {
- const u32 color =
- (shaded ? Truncate32(m_polyline_buffer[buffer_pos++]) : m_render_command.bits) & UINT32_C(0x00FFFFFF);
- read_vertex(end, color);
-
- const GSVector2i start_pos = GSVector2i::load(&start.native_x);
- const GSVector2i end_pos = GSVector2i::load(&end.native_x);
- const GSVector4i rect =
- GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
- {
- DEBUG_LOG("Culling too-large line: {} - {}", start_pos, end_pos);
- }
- else
- {
- AddDrawLineTicks(rect, m_render_command.shading_enable);
-
- cmd->vertices[out_vertex_count++] = start;
- cmd->vertices[out_vertex_count++] = end;
- }
-
- start = end;
- }
-
- if (out_vertex_count > 0)
- {
- DebugAssert(out_vertex_count <= cmd->num_vertices);
- cmd->num_vertices = Truncate16(out_vertex_count);
- GPUBackend::PushCommand(cmd);
- }
- }
- else
- {
- GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand((num_vertices - 1) * 2);
- FillDrawCommand(cmd, m_render_command);
- cmd->palette.bits = 0;
-
- u32 buffer_pos = 0;
- const GPUVertexPosition start_vp{Truncate32(m_polyline_buffer[buffer_pos++])};
- const GSVector2i draw_offset = GSVector2i::load(&m_drawing_offset.x);
- GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
- u32 start_color = m_render_command.color_for_first_vertex;
-
- const bool shaded = m_render_command.shading_enable;
- u32 out_vertex_count = 0;
- for (u32 i = 1; i < num_vertices; i++)
- {
- const u32 end_color = shaded ? (Truncate32(m_polyline_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF))) :
- m_render_command.color_for_first_vertex;
- const GPUVertexPosition vp{Truncate32(m_polyline_buffer[buffer_pos++])};
- const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
-
- const GSVector4i rect =
- GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
- if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT)
- {
- DEBUG_LOG("Culling too-large line: {},{} - {},{}", start_pos.x, start_pos.y, end_pos.x, end_pos.y);
- }
- else
- {
- AddDrawLineTicks(rect, m_render_command.shading_enable);
-
- GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
- out_vertex_count += 2;
-
- GSVector2i::store(&out_vertex[0].x, start_pos);
- out_vertex[0].color = start_color;
- GSVector2i::store(&out_vertex[1].x, end_pos);
- out_vertex[1].color = end_color;
- }
-
- start_pos = end_pos;
- start_color = end_color;
- }
-
- if (out_vertex_count > 0)
- {
- DebugAssert(out_vertex_count <= cmd->num_vertices);
- cmd->num_vertices = Truncate16(out_vertex_count);
- GPUBackend::PushCommand(cmd);
- }
- }
-}
-
-bool GPU::HandleFillRectangleCommand()
-{
- CHECK_COMMAND_SIZE(3);
-
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- const u32 color = FifoPop() & 0x00FFFFFF;
- const u32 dst_x = FifoPeek() & 0x3F0;
- const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
- const u32 width = ((FifoPeek() & VRAM_WIDTH_MASK) + 0xF) & ~0xF;
- const u32 height = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
-
- DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
-
- if (width > 0 && height > 0)
- {
- GPUBackendFillVRAMCommand* cmd = GPUBackend::NewFillVRAMCommand();
- cmd->x = static_cast(dst_x);
- cmd->y = static_cast(dst_y);
- cmd->width = static_cast(width);
- cmd->height = static_cast(height);
- cmd->color = color;
- cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
- cmd->active_line_lsb = m_crtc_state.active_line_lsb;
- GPUBackend::PushCommand(cmd);
- }
-
- AddCommandTicks(46 + ((width / 8) + 9) * height);
- EndCommand();
- return true;
-}
-
-bool GPU::HandleCopyRectangleCPUToVRAMCommand()
-{
- CHECK_COMMAND_SIZE(3);
- m_fifo.RemoveOne();
-
- const u32 coords = FifoPop();
- const u32 size = FifoPop();
-
- // Tenga Seiha does a bunch of completely-invalid VRAM writes on boot, then expects GPU idle to be set.
- // It's unclear what actually happens, I need to write another test, but for now, just skip these uploads.
- // Not setting GPU idle during the write command breaks Doom, so that's not an option.
- if (size == 0xFFFFFFFFu) [[unlikely]]
- {
- ERROR_LOG("Ignoring likely-invalid VRAM write to ({},{})", (coords & VRAM_WIDTH_MASK),
- ((coords >> 16) & VRAM_HEIGHT_MASK));
- return true;
- }
-
- const u32 dst_x = coords & VRAM_WIDTH_MASK;
- const u32 dst_y = (coords >> 16) & VRAM_HEIGHT_MASK;
- const u32 copy_width = ReplaceZero(size & VRAM_WIDTH_MASK, 0x400);
- const u32 copy_height = ReplaceZero((size >> 16) & VRAM_HEIGHT_MASK, 0x200);
- const u32 num_pixels = copy_width * copy_height;
- const u32 num_words = ((num_pixels + 1) / 2);
-
- DEBUG_LOG("Copy rectangle from CPU to VRAM offset=({},{}), size=({},{})", dst_x, dst_y, copy_width, copy_height);
-
- EndCommand();
-
- m_blitter_state = BlitterState::WritingVRAM;
- m_blit_buffer.reserve(num_words);
- m_blit_remaining_words = num_words;
- m_vram_transfer.x = Truncate16(dst_x);
- m_vram_transfer.y = Truncate16(dst_y);
- m_vram_transfer.width = Truncate16(copy_width);
- m_vram_transfer.height = Truncate16(copy_height);
- return true;
-}
-
-void GPU::FinishVRAMWrite()
-{
- if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
- SynchronizeCRTC();
-
- if (m_blit_remaining_words == 0)
- {
- if (g_settings.gpu_dump_cpu_to_vram_copies)
- {
- DumpVRAMToFile(fmt::format("{}" FS_OSPATH_SEPARATOR_STR "cpu_to_vram_copy_{}.png", EmuFolders::DataRoot,
- s_cpu_to_vram_dump_id++),
- m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width,
- m_blit_buffer.data(), true);
- }
-
- UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height,
- m_blit_buffer.data(), m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
- }
- else
- {
- const u32 num_pixels = ZeroExtend32(m_vram_transfer.width) * ZeroExtend32(m_vram_transfer.height);
- const u32 num_words = (num_pixels + 1) / 2;
- const u32 transferred_words = num_words - m_blit_remaining_words;
- const u32 transferred_pixels = transferred_words * 2;
- const u32 transferred_full_rows = transferred_pixels / m_vram_transfer.width;
- const u32 transferred_width_last_row = transferred_pixels % m_vram_transfer.width;
-
- WARNING_LOG("Partial VRAM write - transfer finished with {} of {} words remaining ({} full rows, {} last row)",
- m_blit_remaining_words, num_words, transferred_full_rows, transferred_width_last_row);
-
- const u8* blit_ptr = reinterpret_cast(m_blit_buffer.data());
- if (transferred_full_rows > 0)
- {
- UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, static_cast(transferred_full_rows),
- blit_ptr, m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
- blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16);
- }
- if (transferred_width_last_row > 0)
- {
- UpdateVRAM(m_vram_transfer.x, static_cast(m_vram_transfer.y + transferred_full_rows),
- static_cast(transferred_width_last_row), 1, blit_ptr, m_GPUSTAT.set_mask_while_drawing,
- m_GPUSTAT.check_mask_before_draw);
- }
- }
-
- m_blit_buffer.clear();
- m_vram_transfer = {};
- m_blitter_state = BlitterState::Idle;
-}
-
-bool GPU::HandleCopyRectangleVRAMToCPUCommand()
-{
- CHECK_COMMAND_SIZE(3);
- m_fifo.RemoveOne();
-
- m_vram_transfer.x = Truncate16(FifoPeek() & VRAM_WIDTH_MASK);
- m_vram_transfer.y = Truncate16((FifoPop() >> 16) & VRAM_HEIGHT_MASK);
- m_vram_transfer.width = ((Truncate16(FifoPeek()) - 1) & VRAM_WIDTH_MASK) + 1;
- m_vram_transfer.height = ((Truncate16(FifoPop() >> 16) - 1) & VRAM_HEIGHT_MASK) + 1;
-
- DEBUG_LOG("Copy rectangle from VRAM to CPU offset=({},{}), size=({},{})", m_vram_transfer.x, m_vram_transfer.y,
- m_vram_transfer.width, m_vram_transfer.height);
- DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
-
- // ensure VRAM shadow is up to date
- ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
-
- if (g_settings.gpu_dump_vram_to_cpu_copies)
- {
- DumpVRAMToFile(fmt::format("{}" FS_OSPATH_SEPARATOR_STR "vram_to_cpu_copy_{}.png", EmuFolders::DataRoot,
- s_vram_to_cpu_dump_id++),
- m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * VRAM_WIDTH,
- &g_vram[m_vram_transfer.y * VRAM_WIDTH + m_vram_transfer.x], true);
- }
-
- // switch to pixel-by-pixel read state
- m_blitter_state = BlitterState::ReadingVRAM;
- m_command_total_words = 0;
-
- // toss the entire read in the recorded trace. we might want to change this to mirroring GPUREAD in the future..
- if (m_gpu_dump) [[unlikely]]
- m_gpu_dump->WriteDiscardVRAMRead(m_vram_transfer.width, m_vram_transfer.height);
-
- return true;
-}
-
-bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
-{
- CHECK_COMMAND_SIZE(4);
- m_fifo.RemoveOne();
-
- const u32 src_x = FifoPeek() & VRAM_WIDTH_MASK;
- const u32 src_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
- const u32 dst_x = FifoPeek() & VRAM_WIDTH_MASK;
- const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
- const u32 width = ReplaceZero(FifoPeek() & VRAM_WIDTH_MASK, 0x400);
- const u32 height = ReplaceZero((FifoPop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
-
- DEBUG_LOG("Copy rectangle from VRAM to VRAM src=({},{}), dst=({},{}), size=({},{})", src_x, src_y, dst_x, dst_y,
- width, height);
-
- // Some VRAM copies aren't going to do anything. Most games seem to send a 2x2 VRAM copy at the end of a frame.
- const bool skip_copy =
- width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
- if (!skip_copy)
- {
- GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand();
- cmd->src_x = static_cast(src_x);
- cmd->src_y = static_cast(src_y);
- cmd->dst_x = static_cast(dst_x);
- cmd->dst_y = static_cast(dst_y);
- cmd->width = static_cast(width);
- cmd->height = static_cast(height);
- cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
- cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
- GPUBackend::PushCommand(cmd);
- }
-
- AddCommandTicks(width * height * 2);
- EndCommand();
- return true;
-}