diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 82f4ba80c..c3c7e9f8a 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -1891,7 +1891,7 @@ template u32 Bus::HWHandlers::GPURead(PhysicalMemoryAddress address) { const u32 offset = address & GPU_MASK; - u32 value = g_gpu.ReadRegister(FIXUP_WORD_OFFSET(size, offset)); + u32 value = GPU::ReadRegister(FIXUP_WORD_OFFSET(size, offset)); value = FIXUP_WORD_READ_VALUE(size, offset, value); BUS_CYCLES(2); return value; @@ -1901,7 +1901,7 @@ template void Bus::HWHandlers::GPUWrite(PhysicalMemoryAddress address, u32 value) { const u32 offset = address & GPU_MASK; - g_gpu.WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value)); + GPU::WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value)); } template diff --git a/src/core/dma.cpp b/src/core/dma.cpp index b190ec8f1..b965e9922 100644 --- a/src/core/dma.cpp +++ b/src/core/dma.cpp @@ -821,9 +821,9 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count { case Channel::GPU: { - if (g_gpu.BeginDMAWrite()) [[likely]] + if (GPU::BeginDMAWrite()) [[likely]] { - if (GPUDump::Recorder* dump = g_gpu.GetGPUDump()) [[unlikely]] + if (GPUDump::Recorder* dump = GPU::GetGPUDump()) [[unlikely]] { // No wraparound? dump->BeginGP0Packet(word_count); @@ -850,10 +850,10 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count { u32 value; std::memcpy(&value, &ram_pointer[address], sizeof(u32)); - g_gpu.DMAWrite(address, value); + GPU::DMAWrite(address, value); address = (address + increment) & mask; } - g_gpu.EndDMAWrite(); + GPU::EndDMAWrite(); } } break; @@ -928,7 +928,7 @@ TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count switch (channel) { case Channel::GPU: - g_gpu.DMARead(dest_pointer, word_count); + GPU::DMARead(dest_pointer, word_count); break; case Channel::CDROM: diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 8e048ac87..bcbaa48dc 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "gpu.h" @@ -33,7 +33,9 @@ #include "common/align.h" #include "common/assert.h" +#include "common/bitfield.h" #include "common/error.h" +#include "common/fifo_queue.h" #include "common/file_system.h" #include "common/gsvector_formatter.h" #include "common/log.h" @@ -45,13 +47,353 @@ #include "fmt/format.h" #include "imgui.h" +#include #include +#include +#include #include #include +#include LOG_CHANNEL(GPU); -ALIGN_TO_CACHE_LINE GPU g_gpu; +namespace GPU { + +namespace { +enum class BlitterState : u8 +{ + Idle, + ReadingVRAM, + WritingVRAM, + DrawingPolyLine +}; + +enum : u32 +{ + MAX_FIFO_SIZE = 4096, + DOT_TIMER_INDEX = 0, + HBLANK_TIMER_INDEX = 1, + DRAWING_AREA_COORD_MASK = 1023, +}; + +enum : u16 +{ + NTSC_TICKS_PER_LINE = 3413, + NTSC_TOTAL_LINES = 263, + PAL_TICKS_PER_LINE = 3406, + PAL_TOTAL_LINES = 314, +}; + +enum : u16 +{ + NTSC_HORIZONTAL_ACTIVE_START = 488, + NTSC_HORIZONTAL_ACTIVE_END = 3288, + NTSC_VERTICAL_ACTIVE_START = 16, + NTSC_VERTICAL_ACTIVE_END = 256, + NTSC_OVERSCAN_HORIZONTAL_ACTIVE_START = 608, + NTSC_OVERSCAN_HORIZONTAL_ACTIVE_END = 3168, + NTSC_OVERSCAN_VERTICAL_ACTIVE_START = 24, + NTSC_OVERSCAN_VERTICAL_ACTIVE_END = 248, + PAL_HORIZONTAL_ACTIVE_START = 488, + PAL_HORIZONTAL_ACTIVE_END = 3300, + PAL_VERTICAL_ACTIVE_START = 20, + PAL_VERTICAL_ACTIVE_END = 308, + PAL_OVERSCAN_HORIZONTAL_ACTIVE_START = 628, + PAL_OVERSCAN_HORIZONTAL_ACTIVE_END = 3188, + PAL_OVERSCAN_VERTICAL_ACTIVE_START = 30, + PAL_OVERSCAN_VERTICAL_ACTIVE_END = 298, +}; + +} // namespace + +/// Returns true if no data is being sent from VRAM to the DAC or that no portion of VRAM would be visible on screen. +static bool IsDisplayDisabled(); + +/// Returns true if interlaced rendering is enabled and force progressive scan is disabled. +static bool IsInterlacedRenderingEnabled(); + +/// Returns the number of pending GPU ticks. +TickCount GetPendingCRTCTicks(); +TickCount GetPendingCommandTicks(); + +/// Returns true if a raster scanline or command execution is pending. +static bool IsCommandCompletionPending(); + +static float ComputeHorizontalFrequency(); +static float ComputeVerticalFrequency(); + +// Ticks for hblank/vblank. +static void CRTCTickEvent(void*, TickCount ticks); +static void CommandTickEvent(void*, TickCount ticks); +static void FrameDoneEvent(void*, TickCount ticks); + +// The GPU internally appears to run at 2x the system clock. +// TODO: No, it just draws two pixels per clock. +ALWAYS_INLINE static constexpr TickCount GPUTicksToSystemTicks(TickCount gpu_ticks) +{ + return std::max((gpu_ticks + 1) >> 1, 1); +} +ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) +{ + return sysclk_ticks << 1; +} + +static TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks); +static TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks); + +static bool DumpVRAMToFile(std::string path, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha, + Error* error = nullptr); + +static void SoftReset(); +static void ClearDisplay(); + +// Sets dots per scanline +static void UpdateCRTCConfig(); +static void UpdateCRTCDisplayParameters(); +static void UpdateCRTCHBlankFlag(); + +// Update ticks for this execution slice +static void UpdateCRTCTickEvent(); +static void UpdateCommandTickEvent(); +static u8 UpdateOrGetGPUBusyPct(); + +// Updates dynamic bits in GPUSTAT (ready to send VRAM/ready to receive DMA) +static void UpdateDMARequest(); +static void UpdateGPUIdle(); + +/// Updates drawing area that's suitable for clamping. +static void SetClampedDrawingArea(); + +/// Sets/decodes GP0(E1h) (set draw mode). +static void SetDrawMode(u16 bits); + +/// Sets/decodes polygon/rectangle texture palette value. +static void SetTexturePalette(u16 bits); + +/// Sets/decodes texture window bits. +static void SetTextureWindow(u32 value); + +static u32 ReadGPUREAD(); +static void FinishVRAMWrite(); + +/// Returns the number of vertices in the buffered poly-line. +static u32 GetPolyLineVertexCount(); + +static void AddCommandTicks(TickCount ticks); + +static u32 FifoPop(); +static u32 FifoPeek(); +static u32 FifoPeek(u32 i); + +static void WriteGP1(u32 value); +static void EndCommand(); +static void ExecuteCommands(); +static void TryExecuteCommands(); +static void HandleGetGPUInfoCommand(u32 value); +static void UpdateCLUTIfNeeded(GPUTextureMode texmode, GPUTexturePaletteReg clut); +static void InvalidateCLUT(); + +static void ReadVRAM(u16 x, u16 y, u16 width, u16 height); +static void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask); + +static void PrepareForDraw(); +static void FinishPolyline(); +static void FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc); + +static void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded, bool textured, + bool semitransparent); +static void AddDrawRectangleTicks(const GSVector4i rect, bool textured, bool semitransparent); +static void AddDrawLineTicks(const GSVector4i rect, bool shaded); + +using GP0CommandHandler = bool (*)(); +using GP0CommandHandlerTable = std::array; + +// Rendering commands, returns false if not enough data is provided +static bool HandleUnknownGP0Command(); +static bool HandleNOPCommand(); +static bool HandleClearCacheCommand(); +static bool HandleInterruptRequestCommand(); +static bool HandleSetDrawModeCommand(); +static bool HandleSetTextureWindowCommand(); +static bool HandleSetDrawingAreaTopLeftCommand(); +static bool HandleSetDrawingAreaBottomRightCommand(); +static bool HandleSetDrawingOffsetCommand(); +static bool HandleSetMaskBitCommand(); +static bool HandleRenderPolygonCommand(); +static bool HandleRenderRectangleCommand(); +static bool HandleRenderLineCommand(); +static bool HandleRenderPolyLineCommand(); +static bool HandleFillRectangleCommand(); +static bool HandleCopyRectangleCPUToVRAMCommand(); +static bool HandleCopyRectangleVRAMToCPUCommand(); +static bool HandleCopyRectangleVRAMToVRAMCommand(); + +namespace { +struct DrawMode +{ + static constexpr u16 PALETTE_MASK = UINT16_C(0b0111111111111111); + static constexpr u32 TEXTURE_WINDOW_MASK = UINT32_C(0b11111111111111111111); + + // original values + GPUDrawModeReg mode_reg; + GPUTexturePaletteReg palette_reg; // from vertex + u32 texture_window_value; + + // decoded values + // TODO: Make this a command + GPUTextureWindow texture_window; + bool texture_x_flip; + bool texture_y_flip; +}; + +struct CRTCState +{ + struct Regs + { + static constexpr u32 DISPLAY_ADDRESS_START_MASK = 0b111'11111111'11111110; + static constexpr u32 HORIZONTAL_DISPLAY_RANGE_MASK = 0b11111111'11111111'11111111; + static constexpr u32 VERTICAL_DISPLAY_RANGE_MASK = 0b1111'11111111'11111111; + + union + { + u32 display_address_start; + BitField X; + BitField Y; + }; + union + { + u32 horizontal_display_range; + BitField X1; + BitField X2; + }; + + union + { + u32 vertical_display_range; + BitField Y1; + BitField Y2; + }; + } regs; + + u16 dot_clock_divider; + + // Size of the simulated screen in pixels. Depending on crop mode, this may include overscan area. + u16 display_width; + u16 display_height; + + // Top-left corner in screen coordinates where the outputted portion of VRAM is first visible. + u16 display_origin_left; + u16 display_origin_top; + + // Rectangle in VRAM coordinates describing the area of VRAM that is visible on screen. + u16 display_vram_left; + u16 display_vram_top; + u16 display_vram_width; + u16 display_vram_height; + + // Visible range of the screen, in GPU ticks/lines. Clamped to lie within the active video region. + u16 horizontal_visible_start; + u16 horizontal_visible_end; + u16 vertical_visible_start; + u16 vertical_visible_end; + + u16 horizontal_display_start; + u16 horizontal_display_end; + u16 vertical_display_start; + u16 vertical_display_end; + + u16 horizontal_active_start; + u16 horizontal_active_end; + + u16 horizontal_total; + u16 vertical_total; + + u16 current_scanline; + TickCount fractional_ticks; + TickCount current_tick_in_scanline; + + TickCount fractional_dot_ticks; // only used when timer0 is enabled + + bool in_hblank; + bool in_vblank; + + /// 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...) + u8 interlaced_field; + u8 interlaced_display_field; + + /// 0 if the currently-displayed field is on an even line in VRAM, otherwise 1. + u8 active_line_lsb; +}; + +struct Locals +{ + TimingEvent crtc_tick_event{"GPU CRTC Tick", 1, 1, &GPU::CRTCTickEvent, nullptr}; + TimingEvent command_tick_event{"GPU Command Tick", 1, 1, &GPU::CommandTickEvent, nullptr}; + TimingEvent frame_done_event{"Frame Done", 1, 1, &GPU::FrameDoneEvent, nullptr}; + + GPUSTATReg GPUSTAT = {}; + + bool console_is_pal = false; + bool set_texture_disable_mask = false; + bool drawing_area_changed = false; + bool force_progressive_scan = false; + + DrawMode draw_mode = {}; + + GPUDrawingArea drawing_area = {}; + GPUDrawingOffset drawing_offset = {}; + + GSVector4i clamped_drawing_area = {}; + + CRTCState crtc_state = {}; + + u32 command_total_words = 0; + TickCount pending_command_ticks = 0; + u32 active_ticks_since_last_update = 0; + + /// True if currently executing/syncing. + bool executing_commands = false; + BlitterState blitter_state = BlitterState::Idle; + + struct VRAMTransfer + { + u16 x; + u16 y; + u16 width; + u16 height; + u16 col; + u16 row; + } vram_transfer = {}; + + // One byte free, store the GPU usage here. + u8 last_gpu_busy_pct = 0; + + // These are the bits from the palette register, but zero extended to 32-bit, so we can have an "invalid" value. + // If an extra byte is ever not needed here for padding, the 8-bit flag could be packed into the MSB of this value. + bool current_clut_is_8bit = false; + u32 current_clut_reg_bits = {}; + + /// GPUREAD value for non-VRAM-reads. + u32 GPUREAD_latch = 0; + + std::unique_ptr gpu_dump; + + HeapFIFOQueue fifo; + TickCount max_run_ahead = 128; + u32 fifo_size = 128; + u32 blit_remaining_words; + GPURenderCommand render_command{}; + std::vector blit_buffer; + std::vector polyline_buffer; + + u32 cpu_to_vram_dump_id = 0; + u32 vram_to_cpu_dump_id = 0; +}; +} // namespace + +ALIGN_TO_CACHE_LINE static Locals s_locals; + +} // namespace GPU // aligning VRAM to 4K is fine, since the ARM64 instructions compute 4K page aligned addresses // or it would be, except we want to import the memory for readbacks on metal.. @@ -63,49 +405,36 @@ ALIGN_TO_CACHE_LINE GPU g_gpu; alignas(VRAM_STORAGE_ALIGNMENT) u16 g_vram[VRAM_SIZE / sizeof(u16)]; u16 g_gpu_clut[GPU_CLUT_SIZE]; -const GPU::GP0CommandHandlerTable GPU::s_GP0_command_handler_table = GPU::GenerateGP0CommandHandlerTable(); - -static TimingEvent - s_crtc_tick_event("GPU CRTC Tick", 1, 1, [](void* param, TickCount ticks) { g_gpu.CRTCTickEvent(ticks); }, nullptr); -static TimingEvent s_command_tick_event( - "GPU Command Tick", 1, 1, [](void* param, TickCount ticks) { g_gpu.CommandTickEvent(ticks); }, nullptr); -static TimingEvent - s_frame_done_event("Frame Done", 1, 1, [](void* param, TickCount ticks) { g_gpu.FrameDoneEvent(ticks); }, nullptr); - -GPU::GPU() = default; - -GPU::~GPU() = default; - void GPU::Initialize() { if (!System::IsReplayingGPUDump()) - s_crtc_tick_event.Activate(); + s_locals.crtc_tick_event.Activate(); - m_force_progressive_scan = (g_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive); - m_fifo_size = g_settings.gpu_fifo_size; - m_max_run_ahead = g_settings.gpu_max_run_ahead; - m_console_is_pal = System::IsPALRegion(); + s_locals.force_progressive_scan = (g_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive); + s_locals.fifo_size = g_settings.gpu_fifo_size; + s_locals.max_run_ahead = g_settings.gpu_max_run_ahead; + s_locals.console_is_pal = System::IsPALRegion(); UpdateCRTCConfig(); } void GPU::Shutdown() { - s_command_tick_event.Deactivate(); - s_crtc_tick_event.Deactivate(); - s_frame_done_event.Deactivate(); + s_locals.command_tick_event.Deactivate(); + s_locals.crtc_tick_event.Deactivate(); + s_locals.frame_done_event.Deactivate(); StopRecordingGPUDump(); } void GPU::UpdateSettings(const Settings& old_settings) { - m_force_progressive_scan = (g_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive); - m_fifo_size = g_settings.gpu_fifo_size; - m_max_run_ahead = g_settings.gpu_max_run_ahead; + s_locals.force_progressive_scan = (g_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive); + s_locals.fifo_size = g_settings.gpu_fifo_size; + s_locals.max_run_ahead = g_settings.gpu_max_run_ahead; if (g_settings.gpu_force_video_timing != old_settings.gpu_force_video_timing) { - m_console_is_pal = System::IsPALRegion(); + s_locals.console_is_pal = System::IsPALRegion(); UpdateCRTCConfig(); } else if (g_settings.display_crop_mode != old_settings.display_crop_mode || @@ -124,7 +453,7 @@ void GPU::CPUClockChanged() UpdateCRTCConfig(); } -std::tuple GPU::GetFullDisplayResolution() const +std::pair GPU::GetFullDisplayResolution() { u32 width, height; if (IsDisplayDisabled()) @@ -135,7 +464,7 @@ std::tuple GPU::GetFullDisplayResolution() const else { s32 xmin, xmax, ymin, ymax; - if (!m_GPUSTAT.pal_mode) + if (!s_locals.GPUSTAT.pal_mode) { xmin = NTSC_HORIZONTAL_ACTIVE_START; xmax = NTSC_HORIZONTAL_ACTIVE_END; @@ -150,40 +479,40 @@ std::tuple GPU::GetFullDisplayResolution() const ymax = PAL_VERTICAL_ACTIVE_END; } - width = static_cast(std::max(std::clamp(m_crtc_state.regs.X2, xmin, xmax) - - std::clamp(m_crtc_state.regs.X1, xmin, xmax), + width = static_cast(std::max(std::clamp(s_locals.crtc_state.regs.X2, xmin, xmax) - + std::clamp(s_locals.crtc_state.regs.X1, xmin, xmax), 0) / - m_crtc_state.dot_clock_divider); - height = - static_cast(std::max( - std::clamp(m_crtc_state.regs.Y2, ymin, ymax) - std::clamp(m_crtc_state.regs.Y1, ymin, ymax), 0)) - << BoolToUInt8(m_GPUSTAT.vertical_interlace); + s_locals.crtc_state.dot_clock_divider); + height = static_cast(std::max(std::clamp(s_locals.crtc_state.regs.Y2, ymin, ymax) - + std::clamp(s_locals.crtc_state.regs.Y1, ymin, ymax), + 0)) + << BoolToUInt8(s_locals.GPUSTAT.vertical_interlace); } - return std::tie(width, height); + return std::make_pair(width, height); } void GPU::Reset(bool clear_vram) { - m_GPUSTAT.bits = 0x14802000; - m_set_texture_disable_mask = false; - m_GPUREAD_latch = 0; - m_crtc_state.fractional_ticks = 0; - m_crtc_state.fractional_dot_ticks = 0; - m_crtc_state.current_tick_in_scanline = 0; - m_crtc_state.current_scanline = 0; - m_crtc_state.in_hblank = false; - m_crtc_state.in_vblank = false; - m_crtc_state.interlaced_field = 0; - m_crtc_state.interlaced_display_field = 0; + s_locals.GPUSTAT.bits = 0x14802000; + s_locals.set_texture_disable_mask = false; + s_locals.GPUREAD_latch = 0; + s_locals.crtc_state.fractional_ticks = 0; + s_locals.crtc_state.fractional_dot_ticks = 0; + s_locals.crtc_state.current_tick_in_scanline = 0; + s_locals.crtc_state.current_scanline = 0; + s_locals.crtc_state.in_hblank = false; + s_locals.crtc_state.in_vblank = false; + s_locals.crtc_state.interlaced_field = 0; + s_locals.crtc_state.interlaced_display_field = 0; // Cancel VRAM writes. - m_blitter_state = BlitterState::Idle; - m_active_ticks_since_last_update = 0; + s_locals.blitter_state = BlitterState::Idle; + s_locals.active_ticks_since_last_update = 0; // Force event to reschedule itself. - s_crtc_tick_event.Deactivate(); - s_command_tick_event.Deactivate(); + s_locals.crtc_tick_event.Deactivate(); + s_locals.command_tick_event.Deactivate(); SoftReset(); @@ -194,41 +523,41 @@ void GPU::Reset(bool clear_vram) void GPU::SoftReset() { - if (m_blitter_state == BlitterState::WritingVRAM) + if (s_locals.blitter_state == BlitterState::WritingVRAM) FinishVRAMWrite(); - m_GPUSTAT.texture_page_x_base = 0; - m_GPUSTAT.texture_page_y_base = 0; - m_GPUSTAT.semi_transparency_mode = GPUTransparencyMode::HalfBackgroundPlusHalfForeground; - m_GPUSTAT.texture_color_mode = GPUTextureMode::Palette4Bit; - m_GPUSTAT.dither_enable = false; - m_GPUSTAT.draw_to_displayed_field = false; - m_GPUSTAT.set_mask_while_drawing = false; - m_GPUSTAT.check_mask_before_draw = false; - m_GPUSTAT.reverse_flag = false; - m_GPUSTAT.texture_disable = false; - m_GPUSTAT.horizontal_resolution_2 = 0; - m_GPUSTAT.horizontal_resolution_1 = 0; - m_GPUSTAT.vertical_resolution = false; - m_GPUSTAT.pal_mode = System::IsPALRegion(); - m_GPUSTAT.display_area_color_depth_24 = false; - m_GPUSTAT.vertical_interlace = false; - m_GPUSTAT.display_disable = true; - m_GPUSTAT.dma_direction = GPUDMADirection::Off; - m_drawing_area = {}; - m_drawing_area_changed = true; - m_drawing_offset = {}; - std::memset(&m_crtc_state.regs, 0, sizeof(m_crtc_state.regs)); - m_crtc_state.regs.horizontal_display_range = 0xC60260; - m_crtc_state.regs.vertical_display_range = 0x3FC10; - m_blitter_state = BlitterState::Idle; - m_pending_command_ticks = 0; - m_command_total_words = 0; - m_vram_transfer = {}; - m_fifo.Clear(); - m_blit_buffer.clear(); - m_blit_remaining_words = 0; - m_draw_mode.texture_window_value = 0xFFFFFFFFu; + s_locals.GPUSTAT.texture_page_x_base = 0; + s_locals.GPUSTAT.texture_page_y_base = 0; + s_locals.GPUSTAT.semi_transparency_mode = GPUTransparencyMode::HalfBackgroundPlusHalfForeground; + s_locals.GPUSTAT.texture_color_mode = GPUTextureMode::Palette4Bit; + s_locals.GPUSTAT.dither_enable = false; + s_locals.GPUSTAT.draw_to_displayed_field = false; + s_locals.GPUSTAT.set_mask_while_drawing = false; + s_locals.GPUSTAT.check_mask_before_draw = false; + s_locals.GPUSTAT.reverse_flag = false; + s_locals.GPUSTAT.texture_disable = false; + s_locals.GPUSTAT.horizontal_resolution_2 = 0; + s_locals.GPUSTAT.horizontal_resolution_1 = 0; + s_locals.GPUSTAT.vertical_resolution = false; + s_locals.GPUSTAT.pal_mode = System::IsPALRegion(); + s_locals.GPUSTAT.display_area_color_depth_24 = false; + s_locals.GPUSTAT.vertical_interlace = false; + s_locals.GPUSTAT.display_disable = true; + s_locals.GPUSTAT.dma_direction = GPUDMADirection::Off; + s_locals.drawing_area = {}; + s_locals.drawing_area_changed = true; + s_locals.drawing_offset = {}; + std::memset(&s_locals.crtc_state.regs, 0, sizeof(s_locals.crtc_state.regs)); + s_locals.crtc_state.regs.horizontal_display_range = 0xC60260; + s_locals.crtc_state.regs.vertical_display_range = 0x3FC10; + s_locals.blitter_state = BlitterState::Idle; + s_locals.pending_command_ticks = 0; + s_locals.command_total_words = 0; + s_locals.vram_transfer = {}; + s_locals.fifo.Clear(); + s_locals.blit_buffer.clear(); + s_locals.blit_remaining_words = 0; + s_locals.draw_mode.texture_window_value = 0xFFFFFFFFu; SetDrawMode(0); SetTexturePalette(0); SetTextureWindow(0); @@ -247,11 +576,11 @@ bool GPU::DoState(StateWrapper& sw) ReadVRAM(0, 0, VRAM_WIDTH, VRAM_HEIGHT); } - sw.Do(&m_GPUSTAT.bits); + sw.Do(&s_locals.GPUSTAT.bits); - sw.Do(&m_draw_mode.mode_reg.bits); - sw.Do(&m_draw_mode.palette_reg.bits); - sw.Do(&m_draw_mode.texture_window_value); + sw.Do(&s_locals.draw_mode.mode_reg.bits); + sw.Do(&s_locals.draw_mode.palette_reg.bits); + sw.Do(&s_locals.draw_mode.texture_window_value); if (sw.GetVersion() < 62) [[unlikely]] { @@ -260,60 +589,60 @@ bool GPU::DoState(StateWrapper& sw) sw.SkipBytes(sizeof(u32) * 4); } - sw.Do(&m_draw_mode.texture_window.and_x); - sw.Do(&m_draw_mode.texture_window.and_y); - sw.Do(&m_draw_mode.texture_window.or_x); - sw.Do(&m_draw_mode.texture_window.or_y); - sw.Do(&m_draw_mode.texture_x_flip); - sw.Do(&m_draw_mode.texture_y_flip); - - sw.Do(&m_drawing_area.left); - sw.Do(&m_drawing_area.top); - sw.Do(&m_drawing_area.right); - sw.Do(&m_drawing_area.bottom); - sw.Do(&m_drawing_offset.x); - sw.Do(&m_drawing_offset.y); - sw.Do(&m_drawing_offset.x); - - sw.Do(&m_console_is_pal); - sw.Do(&m_set_texture_disable_mask); - - sw.Do(&m_crtc_state.regs.display_address_start); - sw.Do(&m_crtc_state.regs.horizontal_display_range); - sw.Do(&m_crtc_state.regs.vertical_display_range); - sw.Do(&m_crtc_state.dot_clock_divider); - sw.Do(&m_crtc_state.display_width); - sw.Do(&m_crtc_state.display_height); - sw.Do(&m_crtc_state.display_origin_left); - sw.Do(&m_crtc_state.display_origin_top); - sw.Do(&m_crtc_state.display_vram_left); - sw.Do(&m_crtc_state.display_vram_top); - sw.Do(&m_crtc_state.display_vram_width); - sw.Do(&m_crtc_state.display_vram_height); - sw.Do(&m_crtc_state.horizontal_total); - sw.Do(&m_crtc_state.horizontal_visible_start); - sw.Do(&m_crtc_state.horizontal_visible_end); - sw.Do(&m_crtc_state.horizontal_display_start); - sw.Do(&m_crtc_state.horizontal_display_end); - sw.Do(&m_crtc_state.vertical_total); - sw.Do(&m_crtc_state.vertical_visible_start); - sw.Do(&m_crtc_state.vertical_visible_end); - sw.Do(&m_crtc_state.vertical_display_start); - sw.Do(&m_crtc_state.vertical_display_end); - sw.Do(&m_crtc_state.fractional_ticks); - sw.Do(&m_crtc_state.current_tick_in_scanline); - m_crtc_state.current_scanline = Truncate16(sw.DoValue(static_cast(m_crtc_state.current_scanline))); - sw.DoEx(&m_crtc_state.fractional_dot_ticks, 46, 0); - sw.Do(&m_crtc_state.in_hblank); - sw.Do(&m_crtc_state.in_vblank); - sw.Do(&m_crtc_state.interlaced_field); - sw.Do(&m_crtc_state.interlaced_display_field); - sw.Do(&m_crtc_state.active_line_lsb); - - sw.Do(&m_blitter_state); - sw.Do(&m_pending_command_ticks); - sw.Do(&m_command_total_words); - sw.Do(&m_GPUREAD_latch); + sw.Do(&s_locals.draw_mode.texture_window.and_x); + sw.Do(&s_locals.draw_mode.texture_window.and_y); + sw.Do(&s_locals.draw_mode.texture_window.or_x); + sw.Do(&s_locals.draw_mode.texture_window.or_y); + sw.Do(&s_locals.draw_mode.texture_x_flip); + sw.Do(&s_locals.draw_mode.texture_y_flip); + + sw.Do(&s_locals.drawing_area.left); + sw.Do(&s_locals.drawing_area.top); + sw.Do(&s_locals.drawing_area.right); + sw.Do(&s_locals.drawing_area.bottom); + sw.Do(&s_locals.drawing_offset.x); + sw.Do(&s_locals.drawing_offset.y); + sw.Do(&s_locals.drawing_offset.x); + + sw.Do(&s_locals.console_is_pal); + sw.Do(&s_locals.set_texture_disable_mask); + + sw.Do(&s_locals.crtc_state.regs.display_address_start); + sw.Do(&s_locals.crtc_state.regs.horizontal_display_range); + sw.Do(&s_locals.crtc_state.regs.vertical_display_range); + sw.Do(&s_locals.crtc_state.dot_clock_divider); + sw.Do(&s_locals.crtc_state.display_width); + sw.Do(&s_locals.crtc_state.display_height); + sw.Do(&s_locals.crtc_state.display_origin_left); + sw.Do(&s_locals.crtc_state.display_origin_top); + sw.Do(&s_locals.crtc_state.display_vram_left); + sw.Do(&s_locals.crtc_state.display_vram_top); + sw.Do(&s_locals.crtc_state.display_vram_width); + sw.Do(&s_locals.crtc_state.display_vram_height); + sw.Do(&s_locals.crtc_state.horizontal_total); + sw.Do(&s_locals.crtc_state.horizontal_visible_start); + sw.Do(&s_locals.crtc_state.horizontal_visible_end); + sw.Do(&s_locals.crtc_state.horizontal_display_start); + sw.Do(&s_locals.crtc_state.horizontal_display_end); + sw.Do(&s_locals.crtc_state.vertical_total); + sw.Do(&s_locals.crtc_state.vertical_visible_start); + sw.Do(&s_locals.crtc_state.vertical_visible_end); + sw.Do(&s_locals.crtc_state.vertical_display_start); + sw.Do(&s_locals.crtc_state.vertical_display_end); + sw.Do(&s_locals.crtc_state.fractional_ticks); + sw.Do(&s_locals.crtc_state.current_tick_in_scanline); + s_locals.crtc_state.current_scanline = Truncate16(sw.DoValue(static_cast(s_locals.crtc_state.current_scanline))); + sw.DoEx(&s_locals.crtc_state.fractional_dot_ticks, 46, 0); + sw.Do(&s_locals.crtc_state.in_hblank); + sw.Do(&s_locals.crtc_state.in_vblank); + sw.Do(&s_locals.crtc_state.interlaced_field); + sw.Do(&s_locals.crtc_state.interlaced_display_field); + sw.Do(&s_locals.crtc_state.active_line_lsb); + + sw.Do(&s_locals.blitter_state); + sw.Do(&s_locals.pending_command_ticks); + sw.Do(&s_locals.command_total_words); + sw.Do(&s_locals.GPUREAD_latch); u16 load_clut_data[GPU_CLUT_SIZE]; if (sw.GetVersion() < 64) [[unlikely]] @@ -324,24 +653,24 @@ bool GPU::DoState(StateWrapper& sw) } else { - sw.Do(&m_current_clut_reg_bits); - sw.Do(&m_current_clut_is_8bit); + sw.Do(&s_locals.current_clut_reg_bits); + sw.Do(&s_locals.current_clut_is_8bit); // I hate this extra copy... because I'm a moron and put it in the middle of the state data. sw.DoArray(sw.IsReading() ? load_clut_data : g_gpu_clut, std::size(g_gpu_clut)); } - sw.Do(&m_vram_transfer.x); - sw.Do(&m_vram_transfer.y); - sw.Do(&m_vram_transfer.width); - sw.Do(&m_vram_transfer.height); - sw.Do(&m_vram_transfer.col); - sw.Do(&m_vram_transfer.row); + sw.Do(&s_locals.vram_transfer.x); + sw.Do(&s_locals.vram_transfer.y); + sw.Do(&s_locals.vram_transfer.width); + sw.Do(&s_locals.vram_transfer.height); + sw.Do(&s_locals.vram_transfer.col); + sw.Do(&s_locals.vram_transfer.row); - sw.Do(&m_fifo); - sw.Do(&m_blit_buffer); - sw.Do(&m_blit_remaining_words); - sw.Do(&m_render_command.bits); + sw.Do(&s_locals.fifo); + sw.Do(&s_locals.blit_buffer); + sw.Do(&s_locals.blit_remaining_words); + sw.Do(&s_locals.render_command.bits); if (sw.GetVersion() < 83) [[unlikely]] { @@ -373,7 +702,7 @@ bool GPU::DoState(StateWrapper& sw) std::memcpy(cmd->texture_cache_state, sw.GetData() + vram_start_pos + VRAM_SIZE, tc_data_size); VideoThread::PushCommand(cmd); - m_drawing_area_changed = true; + s_locals.drawing_area_changed = true; SetClampedDrawingArea(); UpdateDMARequest(); UpdateCRTCConfig(); @@ -393,35 +722,35 @@ bool GPU::DoState(StateWrapper& sw) void GPU::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) { - sw.Do(&m_GPUSTAT.bits); + sw.Do(&s_locals.GPUSTAT.bits); - sw.DoBytes(&m_draw_mode, sizeof(m_draw_mode)); - sw.DoBytes(&m_drawing_area, sizeof(m_drawing_area)); - sw.DoBytes(&m_drawing_offset, sizeof(m_drawing_offset)); + sw.DoBytes(&s_locals.draw_mode, sizeof(s_locals.draw_mode)); + sw.DoBytes(&s_locals.drawing_area, sizeof(s_locals.drawing_area)); + sw.DoBytes(&s_locals.drawing_offset, sizeof(s_locals.drawing_offset)); - sw.Do(&m_console_is_pal); - sw.Do(&m_set_texture_disable_mask); + sw.Do(&s_locals.console_is_pal); + sw.Do(&s_locals.set_texture_disable_mask); - sw.DoBytes(&m_crtc_state, sizeof(m_crtc_state)); + sw.DoBytes(&s_locals.crtc_state, sizeof(s_locals.crtc_state)); - sw.Do(&m_blitter_state); - sw.Do(&m_pending_command_ticks); - sw.Do(&m_command_total_words); - sw.Do(&m_GPUREAD_latch); + sw.Do(&s_locals.blitter_state); + sw.Do(&s_locals.pending_command_ticks); + sw.Do(&s_locals.command_total_words); + sw.Do(&s_locals.GPUREAD_latch); - sw.Do(&m_current_clut_reg_bits); - sw.Do(&m_current_clut_is_8bit); + sw.Do(&s_locals.current_clut_reg_bits); + sw.Do(&s_locals.current_clut_is_8bit); - sw.DoBytes(&m_vram_transfer, sizeof(m_vram_transfer)); + sw.DoBytes(&s_locals.vram_transfer, sizeof(s_locals.vram_transfer)); - sw.Do(&m_fifo); - sw.Do(&m_blit_buffer); - sw.Do(&m_blit_remaining_words); - sw.Do(&m_render_command.bits); + sw.Do(&s_locals.fifo); + sw.Do(&s_locals.blit_buffer); + sw.Do(&s_locals.blit_remaining_words); + sw.Do(&s_locals.render_command.bits); if (sw.IsReading()) { - m_drawing_area_changed = true; + s_locals.drawing_area_changed = true; SetClampedDrawingArea(); UpdateDMARequest(); UpdateCRTCConfig(); @@ -438,26 +767,27 @@ void GPU::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) void GPU::UpdateDMARequest() { - switch (m_blitter_state) + switch (s_locals.blitter_state) { case BlitterState::Idle: - m_GPUSTAT.ready_to_send_vram = false; - m_GPUSTAT.ready_to_receive_dma = (m_fifo.IsEmpty() || m_fifo.GetSize() < m_command_total_words); + s_locals.GPUSTAT.ready_to_send_vram = false; + s_locals.GPUSTAT.ready_to_receive_dma = + (s_locals.fifo.IsEmpty() || s_locals.fifo.GetSize() < s_locals.command_total_words); break; case BlitterState::WritingVRAM: - m_GPUSTAT.ready_to_send_vram = false; - m_GPUSTAT.ready_to_receive_dma = (m_fifo.GetSize() < m_fifo_size); + s_locals.GPUSTAT.ready_to_send_vram = false; + s_locals.GPUSTAT.ready_to_receive_dma = (s_locals.fifo.GetSize() < s_locals.fifo_size); break; case BlitterState::ReadingVRAM: - m_GPUSTAT.ready_to_send_vram = true; - m_GPUSTAT.ready_to_receive_dma = false; + s_locals.GPUSTAT.ready_to_send_vram = true; + s_locals.GPUSTAT.ready_to_receive_dma = false; break; case BlitterState::DrawingPolyLine: - m_GPUSTAT.ready_to_send_vram = false; - m_GPUSTAT.ready_to_receive_dma = (m_fifo.GetSize() < m_fifo_size); + s_locals.GPUSTAT.ready_to_send_vram = false; + s_locals.GPUSTAT.ready_to_receive_dma = (s_locals.fifo.GetSize() < s_locals.fifo_size); break; default: @@ -466,35 +796,36 @@ void GPU::UpdateDMARequest() } bool dma_request; - switch (m_GPUSTAT.dma_direction) + switch (s_locals.GPUSTAT.dma_direction) { case GPUDMADirection::Off: dma_request = false; break; case GPUDMADirection::FIFO: - dma_request = m_GPUSTAT.ready_to_receive_dma; + dma_request = s_locals.GPUSTAT.ready_to_receive_dma; break; case GPUDMADirection::CPUtoGP0: - dma_request = m_GPUSTAT.ready_to_receive_dma; + dma_request = s_locals.GPUSTAT.ready_to_receive_dma; break; case GPUDMADirection::GPUREADtoCPU: - dma_request = m_GPUSTAT.ready_to_send_vram; + dma_request = s_locals.GPUSTAT.ready_to_send_vram; break; default: dma_request = false; break; } - m_GPUSTAT.dma_data_request = dma_request; + s_locals.GPUSTAT.dma_data_request = dma_request; DMA::SetRequest(DMA::Channel::GPU, dma_request); } void GPU::UpdateGPUIdle() { - m_GPUSTAT.gpu_idle = (m_blitter_state == BlitterState::Idle && m_pending_command_ticks <= 0 && m_fifo.IsEmpty()); + s_locals.GPUSTAT.gpu_idle = + (s_locals.blitter_state == BlitterState::Idle && s_locals.pending_command_ticks <= 0 && s_locals.fifo.IsEmpty()); } u32 GPU::ReadRegister(u32 offset) @@ -511,9 +842,9 @@ u32 GPU::ReadRegister(u32 offset) if (IsCRTCScanlinePending()) SynchronizeCRTC(); if (IsCommandCompletionPending()) - s_command_tick_event.InvokeEarly(); + s_locals.command_tick_event.InvokeEarly(); - return m_GPUSTAT.bits; + return s_locals.GPUSTAT.bits; } default: @@ -528,31 +859,31 @@ void GPU::WriteRegister(u32 offset, u32 value) { case 0x00: { - if (m_gpu_dump) [[unlikely]] - m_gpu_dump->WriteGP0Packet(value); + if (s_locals.gpu_dump) [[unlikely]] + s_locals.gpu_dump->WriteGP0Packet(value); // FIFO can be overflowed through direct GP0 writes if the command tick event hasn't run, because // there's no backpressure applied to the CPU. Instead force the GPU to run and catch up. - if (m_fifo.GetSize() >= m_fifo_size) [[unlikely]] + if (s_locals.fifo.GetSize() >= s_locals.fifo_size) [[unlikely]] { - s_command_tick_event.InvokeEarly(); + s_locals.command_tick_event.InvokeEarly(); - if (m_fifo.GetSize() >= m_fifo.GetCapacity()) [[unlikely]] + if (s_locals.fifo.GetSize() >= s_locals.fifo.GetCapacity()) [[unlikely]] { - WARNING_LOG("GPU FIFO overflow via GP0 write, size={}", m_fifo.GetSize()); + WARNING_LOG("GPU FIFO overflow via GP0 write, size={}", s_locals.fifo.GetSize()); return; } } - m_fifo.Push(value); + s_locals.fifo.Push(value); ExecuteCommands(); return; } case 0x04: { - if (m_gpu_dump) [[unlikely]] - m_gpu_dump->WriteGP1Packet(value); + if (s_locals.gpu_dump) [[unlikely]] + s_locals.gpu_dump->WriteGP1Packet(value); WriteGP1(value); return; @@ -568,7 +899,7 @@ void GPU::WriteRegister(u32 offset, u32 value) void GPU::DMARead(u32* words, u32 word_count) { - if (m_GPUSTAT.dma_direction != GPUDMADirection::GPUREADtoCPU) + if (s_locals.GPUSTAT.dma_direction != GPUDMADirection::GPUREADtoCPU) { ERROR_LOG("Invalid DMA direction from GPU DMA read"); std::fill_n(words, word_count, UINT32_C(0xFFFFFFFF)); @@ -579,6 +910,17 @@ void GPU::DMARead(u32* words, u32 word_count) words[i] = ReadGPUREAD(); } +bool GPU::BeginDMAWrite() +{ + return (s_locals.GPUSTAT.dma_direction == GPUDMADirection::CPUtoGP0 || + s_locals.GPUSTAT.dma_direction == GPUDMADirection::FIFO); +} + +void GPU::DMAWrite(u32 address, u32 value) +{ + s_locals.fifo.Push((ZeroExtend64(address) << 32) | ZeroExtend64(value)); +} + void GPU::EndDMAWrite() { ExecuteCommands(); @@ -593,24 +935,24 @@ void GPU::EndDMAWrite() * PAL - sysclk * 709379 / 451584 */ -TickCount GPU::GetCRTCFrequency() const +TickCount GPU::GetCRTCFrequency() { - return m_console_is_pal ? 53203425 : 53693175; + return s_locals.console_is_pal ? 53203425 : 53693175; } -TickCount GPU::CRTCTicksToSystemTicks(TickCount gpu_ticks, TickCount fractional_ticks) const +TickCount GPU::CRTCTicksToSystemTicks(TickCount gpu_ticks, TickCount fractional_ticks) { // convert to master clock, rounding up as we want to overshoot not undershoot - if (!m_console_is_pal) + if (!s_locals.console_is_pal) return static_cast((u64(gpu_ticks) * u64(451584) + fractional_ticks + u64(715908)) / u64(715909)); else return static_cast((u64(gpu_ticks) * u64(451584) + fractional_ticks + u64(709378)) / u64(709379)); } -TickCount GPU::SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const +TickCount GPU::SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) { u64 mul = u64(sysclk_ticks); - mul *= !m_console_is_pal ? u64(715909) : u64(709379); + mul *= !s_locals.console_is_pal ? u64(715909) : u64(709379); mul += u64(*fractional_ticks); const TickCount ticks = static_cast(mul / u64(451584)); @@ -620,27 +962,27 @@ TickCount GPU::SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractio void GPU::AddCommandTicks(TickCount ticks) { - m_pending_command_ticks += ticks; - m_active_ticks_since_last_update += ticks; + s_locals.pending_command_ticks += ticks; + s_locals.active_ticks_since_last_update += ticks; } void GPU::SynchronizeCRTC() { - s_crtc_tick_event.InvokeEarly(); + s_locals.crtc_tick_event.InvokeEarly(); } -float GPU::ComputeHorizontalFrequency() const +float GPU::ComputeHorizontalFrequency() { - const CRTCState& cs = m_crtc_state; + const CRTCState& cs = s_locals.crtc_state; TickCount fractional_ticks = 0; return static_cast( static_cast(SystemTicksToCRTCTicks(System::GetTicksPerSecond(), &fractional_ticks)) / static_cast(cs.horizontal_total)); } -float GPU::ComputeVerticalFrequency() const +float GPU::ComputeVerticalFrequency() { - const CRTCState& cs = m_crtc_state; + const CRTCState& cs = s_locals.crtc_state; const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total; TickCount fractional_ticks = 0; return static_cast( @@ -648,15 +990,16 @@ float GPU::ComputeVerticalFrequency() const static_cast(ticks_per_frame)); } -float GPU::ComputePixelAspectRatio() const +float GPU::ComputePixelAspectRatio() { - float sar = (m_crtc_state.display_width > 0 && m_crtc_state.display_height > 0) ? - static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height) : - 1.0f; + float sar = + (s_locals.crtc_state.display_width > 0 && s_locals.crtc_state.display_height > 0) ? + static_cast(s_locals.crtc_state.display_width) / static_cast(s_locals.crtc_state.display_height) : + 1.0f; // Force 4:3 for 24-bit modes option. const DisplayAspectRatio dar_type = - (!g_settings.display_force_4_3_for_24bit || !m_GPUSTAT.display_area_color_depth_24) ? + (!g_settings.display_force_4_3_for_24bit || !s_locals.GPUSTAT.display_area_color_depth_24) ? g_settings.display_aspect_ratio : DisplayAspectRatio::Auto(); float dar = 4.0f / 3.0f; @@ -686,9 +1029,9 @@ float GPU::ComputePixelAspectRatio() const return (dar / sar); } -float GPU::ComputeAspectRatioCorrection() const +float GPU::ComputeAspectRatioCorrection() { - const CRTCState& cs = m_crtc_state; + const CRTCState& cs = s_locals.crtc_state; float relative_width = static_cast(cs.horizontal_visible_end - cs.horizontal_visible_start); float relative_height = static_cast(cs.vertical_visible_end - cs.vertical_visible_start); if (relative_width <= 0 || relative_height <= 0 || g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1()) @@ -701,7 +1044,7 @@ float GPU::ComputeAspectRatioCorrection() const case DisplayCropMode::Borders: case DisplayCropMode::None: { - if (m_GPUSTAT.pal_mode) + if (s_locals.GPUSTAT.pal_mode) { relative_width /= static_cast(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START); relative_height /= static_cast(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START); @@ -716,7 +1059,7 @@ float GPU::ComputeAspectRatioCorrection() const case DisplayCropMode::Overscan: { - if (m_GPUSTAT.pal_mode) + if (s_locals.GPUSTAT.pal_mode) { relative_width /= static_cast(PAL_OVERSCAN_HORIZONTAL_ACTIVE_END - PAL_OVERSCAN_HORIZONTAL_ACTIVE_START); relative_height /= static_cast(PAL_OVERSCAN_VERTICAL_ACTIVE_END - PAL_OVERSCAN_VERTICAL_ACTIVE_START); @@ -783,14 +1126,15 @@ GSVector2 GPU::CalculateRenderWindowSize(DisplayFineCropMode mode, std::span dot_clock_dividers = {{10, 8, 5, 4, 7, 7, 7, 7}}; - CRTCState& cs = m_crtc_state; + CRTCState& cs = s_locals.crtc_state; - cs.vertical_total = m_GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; - cs.horizontal_total = m_GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; - cs.horizontal_active_start = m_GPUSTAT.pal_mode ? PAL_HORIZONTAL_ACTIVE_START : NTSC_HORIZONTAL_ACTIVE_START; - cs.horizontal_active_end = m_GPUSTAT.pal_mode ? PAL_HORIZONTAL_ACTIVE_END : NTSC_HORIZONTAL_ACTIVE_END; + cs.vertical_total = s_locals.GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; + cs.horizontal_total = s_locals.GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; + cs.horizontal_active_start = s_locals.GPUSTAT.pal_mode ? PAL_HORIZONTAL_ACTIVE_START : NTSC_HORIZONTAL_ACTIVE_START; + cs.horizontal_active_end = s_locals.GPUSTAT.pal_mode ? PAL_HORIZONTAL_ACTIVE_END : NTSC_HORIZONTAL_ACTIVE_END; - const u8 horizontal_resolution_index = m_GPUSTAT.horizontal_resolution_1 | (m_GPUSTAT.horizontal_resolution_2 << 2); + const u8 horizontal_resolution_index = + s_locals.GPUSTAT.horizontal_resolution_1 | (s_locals.GPUSTAT.horizontal_resolution_2 << 2); cs.dot_clock_divider = dot_clock_dividers[horizontal_resolution_index]; cs.horizontal_display_start = (std::min(cs.regs.X1, cs.horizontal_total) / cs.dot_clock_divider) * cs.dot_clock_divider; @@ -799,7 +1143,7 @@ void GPU::UpdateCRTCConfig() cs.vertical_display_start = std::min(cs.regs.Y1, cs.vertical_total); cs.vertical_display_end = std::min(cs.regs.Y2, cs.vertical_total); - if (m_GPUSTAT.pal_mode && g_settings.gpu_force_video_timing == ForceVideoTimingMode::NTSC) + if (s_locals.GPUSTAT.pal_mode && g_settings.gpu_force_video_timing == ForceVideoTimingMode::NTSC) { // scale to NTSC parameters cs.horizontal_display_start = @@ -817,7 +1161,7 @@ void GPU::UpdateCRTCConfig() cs.horizontal_total = NTSC_TICKS_PER_LINE; cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; } - else if (!m_GPUSTAT.pal_mode && g_settings.gpu_force_video_timing == ForceVideoTimingMode::PAL) + else if (!s_locals.GPUSTAT.pal_mode && g_settings.gpu_force_video_timing == ForceVideoTimingMode::PAL) { // scale to PAL parameters cs.horizontal_display_start = @@ -847,7 +1191,7 @@ void GPU::UpdateCRTCConfig() cs.horizontal_total = static_cast(System::ScaleTicksToOverclock(static_cast(cs.horizontal_total))); cs.current_tick_in_scanline %= cs.horizontal_total; - cs.UpdateHBlankFlag(); + UpdateCRTCHBlankFlag(); cs.current_scanline %= cs.vertical_total; @@ -859,11 +1203,11 @@ void GPU::UpdateCRTCConfig() void GPU::UpdateCRTCDisplayParameters() { - CRTCState& cs = m_crtc_state; + CRTCState& cs = s_locals.crtc_state; const DisplayCropMode crop_mode = g_settings.display_crop_mode; - const u16 horizontal_total = m_GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; - const u16 vertical_total = m_GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; + const u16 horizontal_total = s_locals.GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; + const u16 vertical_total = s_locals.GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; const u16 horizontal_display_start = (std::min(cs.regs.X1, horizontal_total) / cs.dot_clock_divider) * cs.dot_clock_divider; const u16 horizontal_display_end = @@ -875,7 +1219,7 @@ void GPU::UpdateCRTCDisplayParameters() const u16 old_vertical_visible_start = cs.vertical_visible_start; const u16 old_vertical_visible_end = cs.vertical_visible_end; - if (m_GPUSTAT.pal_mode) + if (s_locals.GPUSTAT.pal_mode) { // TODO: Verify PAL numbers. switch (crop_mode) @@ -965,10 +1309,10 @@ void GPU::UpdateCRTCDisplayParameters() // If force-progressive is enabled, we only double the height in 480i mode. This way non-interleaved 480i framebuffers // won't be broken when displayed. - const u8 y_shift = BoolToUInt8(m_GPUSTAT.vertical_interlace && m_GPUSTAT.vertical_resolution); - const u8 height_shift = m_force_progressive_scan ? y_shift : BoolToUInt8(m_GPUSTAT.vertical_interlace); - const u16 old_vram_width = m_crtc_state.display_vram_width; - const u16 old_vram_height = m_crtc_state.display_vram_height; + const u8 y_shift = BoolToUInt8(s_locals.GPUSTAT.vertical_interlace && s_locals.GPUSTAT.vertical_resolution); + const u8 height_shift = s_locals.force_progressive_scan ? y_shift : BoolToUInt8(s_locals.GPUSTAT.vertical_interlace); + const u16 old_vram_width = s_locals.crtc_state.display_vram_width; + const u16 old_vram_height = s_locals.crtc_state.display_vram_height; // Determine screen size. cs.display_width = (cs.horizontal_visible_end - cs.horizontal_visible_start) / cs.dot_clock_divider; @@ -1047,50 +1391,71 @@ void GPU::UpdateCRTCDisplayParameters() } } -GSVector2i GPU::GetCRTCVideoSize() const +GSVector2i GPU::GetCRTCVideoSize() { // Verify assumptions about struct layout. static_assert(offsetof(CRTCState, display_width) + sizeof(u16) == offsetof(CRTCState, display_height)); - return GSVector2i::load32(&m_crtc_state.display_width).u16to32(); + return GSVector2i::load32(&s_locals.crtc_state.display_width).u16to32(); } -GSVector4i GPU::GetCRTCVideoActiveRect() const +GSVector4i GPU::GetCRTCVideoActiveRect() { static_assert(offsetof(CRTCState, display_origin_left) + sizeof(u16) == offsetof(CRTCState, display_origin_top) && offsetof(CRTCState, display_vram_width) + sizeof(u16) == offsetof(CRTCState, display_vram_height)); - const GSVector2i origin = GSVector2i::load32(&m_crtc_state.display_origin_left).u16to32(); - const GSVector2i size = GSVector2i::load32(&m_crtc_state.display_vram_width).u16to32(); + const GSVector2i origin = GSVector2i::load32(&s_locals.crtc_state.display_origin_left).u16to32(); + const GSVector2i size = GSVector2i::load32(&s_locals.crtc_state.display_vram_width).u16to32(); return GSVector4i::xyxy(origin, origin.add32(size)); } -GSVector4i GPU::GetCRTCVRAMSourceRect() const +GSVector4i GPU::GetCRTCVRAMSourceRect() { static_assert(offsetof(CRTCState, display_vram_left) + sizeof(u16) == offsetof(CRTCState, display_vram_top) && offsetof(CRTCState, display_vram_top) + sizeof(u16) == offsetof(CRTCState, display_vram_width) && offsetof(CRTCState, display_vram_width) + sizeof(u16) == offsetof(CRTCState, display_vram_height)); - const GSVector4i rc = GSVector4i::loadl(&m_crtc_state.display_vram_left).u16to32(); + const GSVector4i rc = GSVector4i::loadl(&s_locals.crtc_state.display_vram_left).u16to32(); const GSVector2i origin = rc.xy(); return GSVector4i::xyxy(origin, origin.add32(rc.zw())); } -TickCount GPU::GetPendingCRTCTicks() const +ALWAYS_INLINE bool GPU::IsDisplayDisabled() { - const TickCount pending_sysclk_ticks = s_crtc_tick_event.GetTicksSinceLastExecution(); - TickCount fractional_ticks = m_crtc_state.fractional_ticks; - return SystemTicksToCRTCTicks(pending_sysclk_ticks, &fractional_ticks); + return s_locals.GPUSTAT.display_disable || s_locals.crtc_state.display_vram_width == 0 || + s_locals.crtc_state.display_vram_height == 0; } -TickCount GPU::GetPendingCommandTicks() const +bool GPU::IsInterlacedDisplayEnabled() { - if (!s_command_tick_event.IsActive()) - return 0; + return (!s_locals.force_progressive_scan && s_locals.GPUSTAT.vertical_interlace); +} + +bool GPU::IsProgressiveDisplayScanForced() +{ + return (s_locals.force_progressive_scan && s_locals.GPUSTAT.vertical_interlace); +} - return SystemTicksToGPUTicks(s_command_tick_event.GetTicksSinceLastExecution()); +ALWAYS_INLINE bool GPU::IsInterlacedRenderingEnabled() +{ + return (!s_locals.force_progressive_scan && s_locals.GPUSTAT.SkipDrawingToActiveField()); +} + +bool GPU::IsInPALMode() +{ + return s_locals.GPUSTAT.pal_mode; +} + +ALWAYS_INLINE_RELEASE TickCount GPU::GetPendingCRTCTicks() +{ + const TickCount pending_sysclk_ticks = s_locals.crtc_tick_event.GetTicksSinceLastExecution(); + TickCount fractional_ticks = s_locals.crtc_state.fractional_ticks; + return SystemTicksToCRTCTicks(pending_sysclk_ticks, &fractional_ticks); } -TickCount GPU::GetRemainingCommandTicks() const +ALWAYS_INLINE_RELEASE TickCount GPU::GetPendingCommandTicks() { - return std::max(m_pending_command_ticks - GetPendingCommandTicks(), 0); + if (!s_locals.command_tick_event.IsActive()) + return 0; + + return SystemTicksToGPUTicks(s_locals.command_tick_event.GetTicksSinceLastExecution()); } void GPU::UpdateCRTCTickEvent() @@ -1100,28 +1465,28 @@ void GPU::UpdateCRTCTickEvent() if (Timers::IsSyncEnabled(HBLANK_TIMER_INDEX)) { // when the timer sync is enabled we need to sync at vblank start and end - lines_until_event = - (m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end) ? - (m_crtc_state.vertical_total - m_crtc_state.current_scanline + m_crtc_state.vertical_display_start) : - (m_crtc_state.vertical_display_end - m_crtc_state.current_scanline); + lines_until_event = (s_locals.crtc_state.current_scanline >= s_locals.crtc_state.vertical_display_end) ? + (s_locals.crtc_state.vertical_total - s_locals.crtc_state.current_scanline + + s_locals.crtc_state.vertical_display_start) : + (s_locals.crtc_state.vertical_display_end - s_locals.crtc_state.current_scanline); } else { - lines_until_event = - (m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end ? - (m_crtc_state.vertical_total - m_crtc_state.current_scanline + m_crtc_state.vertical_display_end) : - (m_crtc_state.vertical_display_end - m_crtc_state.current_scanline)); + lines_until_event = (s_locals.crtc_state.current_scanline >= s_locals.crtc_state.vertical_display_end ? + (s_locals.crtc_state.vertical_total - s_locals.crtc_state.current_scanline + + s_locals.crtc_state.vertical_display_end) : + (s_locals.crtc_state.vertical_display_end - s_locals.crtc_state.current_scanline)); } if (Timers::IsExternalIRQEnabled(HBLANK_TIMER_INDEX)) lines_until_event = std::min(lines_until_event, Timers::GetTicksUntilIRQ(HBLANK_TIMER_INDEX)); TickCount ticks_until_event = - lines_until_event * m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline; + lines_until_event * s_locals.crtc_state.horizontal_total - s_locals.crtc_state.current_tick_in_scanline; if (Timers::IsExternalIRQEnabled(DOT_TIMER_INDEX)) { const TickCount dots_until_irq = Timers::GetTicksUntilIRQ(DOT_TIMER_INDEX); const TickCount ticks_until_irq = - (dots_until_irq * m_crtc_state.dot_clock_divider) - m_crtc_state.fractional_dot_ticks; + (dots_until_irq * s_locals.crtc_state.dot_clock_divider) - s_locals.crtc_state.fractional_dot_ticks; ticks_until_event = std::min(ticks_until_event, std::max(ticks_until_irq, 0)); } @@ -1130,65 +1495,76 @@ void GPU::UpdateCRTCTickEvent() // This could potentially be optimized to skip the time the gate is active, if we're resetting and free running. // But realistically, I've only seen sync off (most games), or reset+pause on gate (Konami Lightgun games). TickCount ticks_until_hblank_start_or_end; - if (m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_active_end) + if (s_locals.crtc_state.current_tick_in_scanline >= s_locals.crtc_state.horizontal_active_end) { - ticks_until_hblank_start_or_end = - m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline + m_crtc_state.horizontal_active_start; + ticks_until_hblank_start_or_end = s_locals.crtc_state.horizontal_total - + s_locals.crtc_state.current_tick_in_scanline + + s_locals.crtc_state.horizontal_active_start; } - else if (m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_active_start) + else if (s_locals.crtc_state.current_tick_in_scanline < s_locals.crtc_state.horizontal_active_start) { - ticks_until_hblank_start_or_end = m_crtc_state.horizontal_active_start - m_crtc_state.current_tick_in_scanline; + ticks_until_hblank_start_or_end = + s_locals.crtc_state.horizontal_active_start - s_locals.crtc_state.current_tick_in_scanline; } else { - ticks_until_hblank_start_or_end = m_crtc_state.horizontal_active_end - m_crtc_state.current_tick_in_scanline; + ticks_until_hblank_start_or_end = + s_locals.crtc_state.horizontal_active_end - s_locals.crtc_state.current_tick_in_scanline; } ticks_until_event = std::min(ticks_until_event, ticks_until_hblank_start_or_end); } if (!System::IsReplayingGPUDump()) [[likely]] - s_crtc_tick_event.Schedule(CRTCTicksToSystemTicks(ticks_until_event, m_crtc_state.fractional_ticks)); + s_locals.crtc_tick_event.Schedule(CRTCTicksToSystemTicks(ticks_until_event, s_locals.crtc_state.fractional_ticks)); } -bool GPU::IsCRTCScanlinePending() const +bool GPU::IsCRTCScanlinePending() { // TODO: Most of these should be fields, not lines. - const TickCount ticks = (GetPendingCRTCTicks() + m_crtc_state.current_tick_in_scanline); - return (ticks >= m_crtc_state.horizontal_total); + const TickCount ticks = (GetPendingCRTCTicks() + s_locals.crtc_state.current_tick_in_scanline); + return (ticks >= s_locals.crtc_state.horizontal_total); } -bool GPU::IsCommandCompletionPending() const +ALWAYS_INLINE void GPU::UpdateCRTCHBlankFlag() { - return (m_pending_command_ticks > 0 && GetPendingCommandTicks() >= m_pending_command_ticks); + s_locals.crtc_state.in_hblank = + (s_locals.crtc_state.current_tick_in_scanline < s_locals.crtc_state.horizontal_active_start || + s_locals.crtc_state.current_tick_in_scanline >= s_locals.crtc_state.horizontal_active_end); } -void GPU::CRTCTickEvent(TickCount ticks) +ALWAYS_INLINE_RELEASE bool GPU::IsCommandCompletionPending() +{ + return (s_locals.pending_command_ticks > 0 && GetPendingCommandTicks() >= s_locals.pending_command_ticks); +} + +void GPU::CRTCTickEvent(void*, TickCount ticks) { // convert cpu/master clock to GPU ticks, accounting for partial cycles because of the non-integer divider - const TickCount prev_tick = m_crtc_state.current_tick_in_scanline; - const TickCount gpu_ticks = SystemTicksToCRTCTicks(ticks, &m_crtc_state.fractional_ticks); - m_crtc_state.current_tick_in_scanline += gpu_ticks; + const TickCount prev_tick = s_locals.crtc_state.current_tick_in_scanline; + const TickCount gpu_ticks = SystemTicksToCRTCTicks(ticks, &s_locals.crtc_state.fractional_ticks); + s_locals.crtc_state.current_tick_in_scanline += gpu_ticks; if (Timers::IsUsingExternalClock(DOT_TIMER_INDEX)) { - m_crtc_state.fractional_dot_ticks += gpu_ticks; - const TickCount dots = m_crtc_state.fractional_dot_ticks / m_crtc_state.dot_clock_divider; - m_crtc_state.fractional_dot_ticks = m_crtc_state.fractional_dot_ticks % m_crtc_state.dot_clock_divider; + s_locals.crtc_state.fractional_dot_ticks += gpu_ticks; + const TickCount dots = s_locals.crtc_state.fractional_dot_ticks / s_locals.crtc_state.dot_clock_divider; + s_locals.crtc_state.fractional_dot_ticks = + s_locals.crtc_state.fractional_dot_ticks % s_locals.crtc_state.dot_clock_divider; if (dots > 0) Timers::AddTicks(DOT_TIMER_INDEX, dots); } - if (m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_total) + if (s_locals.crtc_state.current_tick_in_scanline < s_locals.crtc_state.horizontal_total) { // short path when we execute <1 line.. this shouldn't occur often, except when gated (konami lightgun games). - m_crtc_state.UpdateHBlankFlag(); - Timers::SetGate(DOT_TIMER_INDEX, m_crtc_state.in_hblank); + UpdateCRTCHBlankFlag(); + Timers::SetGate(DOT_TIMER_INDEX, s_locals.crtc_state.in_hblank); if (Timers::IsUsingExternalClock(HBLANK_TIMER_INDEX)) { const u32 hblank_timer_ticks = - BoolToUInt32(m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_active_end) - - BoolToUInt32(prev_tick >= m_crtc_state.horizontal_active_end); + BoolToUInt32(s_locals.crtc_state.current_tick_in_scanline >= s_locals.crtc_state.horizontal_active_end) - + BoolToUInt32(prev_tick >= s_locals.crtc_state.horizontal_active_end); if (hblank_timer_ticks > 0) Timers::AddTicks(HBLANK_TIMER_INDEX, static_cast(hblank_timer_ticks)); } @@ -1197,15 +1573,15 @@ void GPU::CRTCTickEvent(TickCount ticks) return; } - u32 lines_to_draw = m_crtc_state.current_tick_in_scanline / m_crtc_state.horizontal_total; - m_crtc_state.current_tick_in_scanline %= m_crtc_state.horizontal_total; + u32 lines_to_draw = s_locals.crtc_state.current_tick_in_scanline / s_locals.crtc_state.horizontal_total; + s_locals.crtc_state.current_tick_in_scanline %= s_locals.crtc_state.horizontal_total; #if 0 - Log_WarningPrintf("Old line: %u, new line: %u, drawing %u", m_crtc_state.current_scanline, - m_crtc_state.current_scanline + lines_to_draw, lines_to_draw); + WARNING_LOG("Old line: {}, new line: {}, drawing {}", s_locals.crtc_state.current_scanline, + s_locals.crtc_state.current_scanline + lines_to_draw, lines_to_draw); #endif - m_crtc_state.UpdateHBlankFlag(); - Timers::SetGate(DOT_TIMER_INDEX, m_crtc_state.in_hblank); + UpdateCRTCHBlankFlag(); + Timers::SetGate(DOT_TIMER_INDEX, s_locals.crtc_state.in_hblank); if (Timers::IsUsingExternalClock(HBLANK_TIMER_INDEX)) { @@ -1214,8 +1590,8 @@ void GPU::CRTCTickEvent(TickCount ticks) // horizontal_active_start, we still want to add one, because hblank would have gone inactive, and then active again // during the line. Finally add the current line being drawn, if hblank went inactive->active during the line. const u32 hblank_timer_ticks = - lines_to_draw - BoolToUInt32(prev_tick >= m_crtc_state.horizontal_active_end) + - BoolToUInt32(m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_active_end); + lines_to_draw - BoolToUInt32(prev_tick >= s_locals.crtc_state.horizontal_active_end) + + BoolToUInt32(s_locals.crtc_state.current_tick_in_scanline >= s_locals.crtc_state.horizontal_active_end); if (hblank_timer_ticks > 0) Timers::AddTicks(HBLANK_TIMER_INDEX, static_cast(hblank_timer_ticks)); } @@ -1223,34 +1599,34 @@ void GPU::CRTCTickEvent(TickCount ticks) bool frame_done = false; while (lines_to_draw > 0) { - const u32 lines_to_draw_this_loop = - std::min(lines_to_draw, static_cast(m_crtc_state.vertical_total - m_crtc_state.current_scanline)); - const u32 prev_scanline = m_crtc_state.current_scanline; - m_crtc_state.current_scanline = Truncate16(m_crtc_state.current_scanline + lines_to_draw_this_loop); - DebugAssert(m_crtc_state.current_scanline <= m_crtc_state.vertical_total); + const u32 lines_to_draw_this_loop = std::min( + lines_to_draw, static_cast(s_locals.crtc_state.vertical_total - s_locals.crtc_state.current_scanline)); + const u32 prev_scanline = s_locals.crtc_state.current_scanline; + s_locals.crtc_state.current_scanline = Truncate16(s_locals.crtc_state.current_scanline + lines_to_draw_this_loop); + DebugAssert(s_locals.crtc_state.current_scanline <= s_locals.crtc_state.vertical_total); lines_to_draw -= lines_to_draw_this_loop; // clear the vblank flag if the beam would pass through the display area - if (prev_scanline < m_crtc_state.vertical_display_start && - m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end) + if (prev_scanline < s_locals.crtc_state.vertical_display_start && + s_locals.crtc_state.current_scanline >= s_locals.crtc_state.vertical_display_end) { Timers::SetGate(HBLANK_TIMER_INDEX, false); InterruptController::SetLineState(InterruptController::IRQ::VBLANK, false); - m_crtc_state.in_vblank = false; + s_locals.crtc_state.in_vblank = false; } - const bool new_vblank = m_crtc_state.current_scanline < m_crtc_state.vertical_display_start || - m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end; - if (m_crtc_state.in_vblank != new_vblank) + const bool new_vblank = s_locals.crtc_state.current_scanline < s_locals.crtc_state.vertical_display_start || + s_locals.crtc_state.current_scanline >= s_locals.crtc_state.vertical_display_end; + if (s_locals.crtc_state.in_vblank != new_vblank) { if (new_vblank) { DEBUG_LOG("Now in v-blank"); - if (m_gpu_dump) [[unlikely]] + if (s_locals.gpu_dump) [[unlikely]] { - m_gpu_dump->WriteVSync(System::GetGlobalTickCounter()); - if (m_gpu_dump->IsFinished()) [[unlikely]] + s_locals.gpu_dump->WriteVSync(System::GetGlobalTickCounter()); + if (s_locals.gpu_dump->IsFinished()) [[unlikely]] StopRecordingGPUDump(); } @@ -1261,47 +1637,50 @@ void GPU::CRTCTickEvent(TickCount ticks) frame_done = true; // switch fields early. this is needed so we draw to the correct one. - if (m_GPUSTAT.InInterleaved480iMode()) - m_crtc_state.interlaced_display_field = m_crtc_state.interlaced_field ^ 1u; + if (s_locals.GPUSTAT.InInterleaved480iMode()) + s_locals.crtc_state.interlaced_display_field = s_locals.crtc_state.interlaced_field ^ 1u; else - m_crtc_state.interlaced_display_field = 0; + s_locals.crtc_state.interlaced_display_field = 0; } Timers::SetGate(HBLANK_TIMER_INDEX, new_vblank); InterruptController::SetLineState(InterruptController::IRQ::VBLANK, new_vblank); - m_crtc_state.in_vblank = new_vblank; + s_locals.crtc_state.in_vblank = new_vblank; } // past the end of vblank? - if (m_crtc_state.current_scanline == m_crtc_state.vertical_total) + if (s_locals.crtc_state.current_scanline == s_locals.crtc_state.vertical_total) { // start the new frame - m_crtc_state.current_scanline = 0; - if (m_GPUSTAT.vertical_interlace) + s_locals.crtc_state.current_scanline = 0; + if (s_locals.GPUSTAT.vertical_interlace) { - m_crtc_state.interlaced_field ^= 1u; - m_GPUSTAT.interlaced_field = BoolToUInt8(!ConvertToBoolUnchecked(m_crtc_state.interlaced_field)); + s_locals.crtc_state.interlaced_field ^= 1u; + s_locals.GPUSTAT.interlaced_field = BoolToUInt8(!ConvertToBoolUnchecked(s_locals.crtc_state.interlaced_field)); } else { - m_crtc_state.interlaced_field = 0; - m_GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0 + s_locals.crtc_state.interlaced_field = 0; + s_locals.GPUSTAT.interlaced_field = 0u; // new GPU = 1, old GPU = 0 } } } // alternating even line bit in 240-line mode - if (m_GPUSTAT.InInterleaved480iMode()) + if (s_locals.GPUSTAT.InInterleaved480iMode()) { - m_crtc_state.active_line_lsb = - Truncate8((m_crtc_state.regs.Y + BoolToUInt32(m_crtc_state.interlaced_display_field)) & u32(1)); - m_GPUSTAT.display_line_lsb = ConvertToBoolUnchecked( - (m_crtc_state.regs.Y + (BoolToUInt8(!m_crtc_state.in_vblank) & m_crtc_state.interlaced_display_field)) & u32(1)); + s_locals.crtc_state.active_line_lsb = + Truncate8((s_locals.crtc_state.regs.Y + BoolToUInt32(s_locals.crtc_state.interlaced_display_field)) & u32(1)); + s_locals.GPUSTAT.display_line_lsb = + ConvertToBoolUnchecked((s_locals.crtc_state.regs.Y + (BoolToUInt8(!s_locals.crtc_state.in_vblank) & + s_locals.crtc_state.interlaced_display_field)) & + u32(1)); } else { - m_crtc_state.active_line_lsb = 0; - m_GPUSTAT.display_line_lsb = ConvertToBoolUnchecked((m_crtc_state.regs.Y + m_crtc_state.current_scanline) & u32(1)); + s_locals.crtc_state.active_line_lsb = 0; + s_locals.GPUSTAT.display_line_lsb = + ConvertToBoolUnchecked((s_locals.crtc_state.regs.Y + s_locals.crtc_state.current_scanline) & u32(1)); } UpdateCRTCTickEvent(); @@ -1313,7 +1692,7 @@ void GPU::CRTCTickEvent(TickCount ticks) if (!TimingEvents::IsRunningEvents()) [[unlikely]] { DEBUG_LOG("Deferring frame done call"); - s_frame_done_event.Schedule(0); + s_locals.frame_done_event.Schedule(0); } else { @@ -1322,57 +1701,57 @@ void GPU::CRTCTickEvent(TickCount ticks) } } -void GPU::CommandTickEvent(TickCount ticks) +void GPU::CommandTickEvent(void*, TickCount ticks) { - m_pending_command_ticks -= SystemTicksToGPUTicks(ticks); + s_locals.pending_command_ticks -= SystemTicksToGPUTicks(ticks); - m_executing_commands = true; + s_locals.executing_commands = true; ExecuteCommands(); UpdateCommandTickEvent(); - m_executing_commands = false; + s_locals.executing_commands = false; } -void GPU::FrameDoneEvent(TickCount ticks) +void GPU::FrameDoneEvent(void*, TickCount ticks) { DebugAssert(TimingEvents::IsRunningEvents()); - s_frame_done_event.Deactivate(); + s_locals.frame_done_event.Deactivate(); System::FrameDone(); } void GPU::UpdateCommandTickEvent() { - if (m_pending_command_ticks <= 0) + if (s_locals.pending_command_ticks <= 0) { - m_pending_command_ticks = 0; - s_command_tick_event.Deactivate(); + s_locals.pending_command_ticks = 0; + s_locals.command_tick_event.Deactivate(); } else { - s_command_tick_event.SetIntervalAndSchedule(GPUTicksToSystemTicks(m_pending_command_ticks)); + s_locals.command_tick_event.SetIntervalAndSchedule(GPUTicksToSystemTicks(s_locals.pending_command_ticks)); } } u8 GPU::UpdateOrGetGPUBusyPct() { const u32 frame_number = System::GetFrameNumber(); - if ((m_GPUSTAT.pal_mode ? (frame_number % 50) : (frame_number % 60)) != 0) [[likely]] - return m_last_gpu_busy_pct; + if ((s_locals.GPUSTAT.pal_mode ? (frame_number % 50) : (frame_number % 60)) != 0) [[likely]] + return s_locals.last_gpu_busy_pct; const double busy_frac = - static_cast(m_active_ticks_since_last_update) / + static_cast(s_locals.active_ticks_since_last_update) / static_cast(SystemTicksToGPUTicks(System::ScaleTicksToOverclock(System::MASTER_CLOCK)) * - (ComputeVerticalFrequency() / (m_GPUSTAT.pal_mode ? 50.0f : 60.0f))); + (ComputeVerticalFrequency() / (s_locals.GPUSTAT.pal_mode ? 50.0f : 60.0f))); const double usage_pct = busy_frac * 100.0; DEBUG_LOG("PSX GPU Usage: {:.2f}% [{:.0f} cycles avg per frame]", usage_pct, - static_cast(m_active_ticks_since_last_update) / (m_GPUSTAT.pal_mode ? 50.0f : 60.0f)); - m_active_ticks_since_last_update = 0; + static_cast(s_locals.active_ticks_since_last_update) / (s_locals.GPUSTAT.pal_mode ? 50.0f : 60.0f)); + s_locals.active_ticks_since_last_update = 0; - m_last_gpu_busy_pct = static_cast(std::min(std::round(usage_pct), 100)); - return m_last_gpu_busy_pct; + s_locals.last_gpu_busy_pct = static_cast(std::min(std::round(usage_pct), 100)); + return s_locals.last_gpu_busy_pct; } -GSVector2 GPU::ConvertScreenCoordinatesToDisplayCoordinates(GSVector2 window_pos) const +GSVector2 GPU::ConvertScreenCoordinatesToDisplayCoordinates(GSVector2 window_pos) { const WindowInfo& wi = VideoThread::GetRenderWindowInfo(); if (wi.IsSurfaceless()) @@ -1406,38 +1785,38 @@ GSVector2 GPU::ConvertScreenCoordinatesToDisplayCoordinates(GSVector2 window_pos } bool GPU::ConvertDisplayCoordinatesToBeamTicksAndLines(const GSVector2& display_pos, float x_scale, u32* out_tick, - u32* out_line) const + u32* out_line) { float display_x = display_pos.x; float display_y = display_pos.y; if (x_scale != 1.0f) { - const float dw = static_cast(m_crtc_state.display_width); + const float dw = static_cast(s_locals.crtc_state.display_width); float scaled_x = ((display_x / dw) * 2.0f) - 1.0f; // 0..1 -> -1..1 scaled_x *= x_scale; display_x = (((scaled_x + 1.0f) * 0.5f) * dw); // -1..1 -> 0..1 } - if (display_x < 0 || static_cast(display_x) >= m_crtc_state.display_width || display_y < 0 || - static_cast(display_y) >= m_crtc_state.display_height) + if (display_x < 0 || static_cast(display_x) >= s_locals.crtc_state.display_width || display_y < 0 || + static_cast(display_y) >= s_locals.crtc_state.display_height) { return false; } - *out_line = (static_cast(std::round(display_y)) >> BoolToUInt8(m_GPUSTAT.vertical_interlace)) + - m_crtc_state.vertical_visible_start; - *out_tick = static_cast(System::ScaleTicksToOverclock( - static_cast(std::round(display_x * static_cast(m_crtc_state.dot_clock_divider))))) + - m_crtc_state.horizontal_visible_start; + *out_line = (static_cast(std::round(display_y)) >> BoolToUInt8(s_locals.GPUSTAT.vertical_interlace)) + + s_locals.crtc_state.vertical_visible_start; + *out_tick = static_cast(System::ScaleTicksToOverclock(static_cast( + std::round(display_x * static_cast(s_locals.crtc_state.dot_clock_divider))))) + + s_locals.crtc_state.horizontal_visible_start; return true; } void GPU::GetBeamPosition(u32* out_ticks, u32* out_line) { - const u32 current_tick = (GetPendingCRTCTicks() + m_crtc_state.current_tick_in_scanline); - *out_line = - (m_crtc_state.current_scanline + (current_tick / m_crtc_state.horizontal_total)) % m_crtc_state.vertical_total; - *out_ticks = current_tick % m_crtc_state.horizontal_total; + const u32 current_tick = (GetPendingCRTCTicks() + s_locals.crtc_state.current_tick_in_scanline); + *out_line = (s_locals.crtc_state.current_scanline + (current_tick / s_locals.crtc_state.horizontal_total)) % + s_locals.crtc_state.vertical_total; + *out_ticks = current_tick % s_locals.crtc_state.horizontal_total; } TickCount GPU::GetSystemTicksUntilTicksAndLine(u32 ticks, u32 line) @@ -1452,42 +1831,52 @@ TickCount GPU::GetSystemTicksUntilTicksAndLine(u32 ticks, u32 line) } else { - ticks_to_target = (m_crtc_state.horizontal_total - current_tick) + ticks; - current_line = (current_line + 1) % m_crtc_state.vertical_total; + ticks_to_target = (s_locals.crtc_state.horizontal_total - current_tick) + ticks; + current_line = (current_line + 1) % s_locals.crtc_state.vertical_total; } const u32 lines_to_target = - (line >= current_line) ? (line - current_line) : ((m_crtc_state.vertical_total - current_line) + line); + (line >= current_line) ? (line - current_line) : ((s_locals.crtc_state.vertical_total - current_line) + line); const TickCount total_ticks_to_target = - static_cast((lines_to_target * m_crtc_state.horizontal_total) + ticks_to_target); + static_cast((lines_to_target * s_locals.crtc_state.horizontal_total) + ticks_to_target); + + return CRTCTicksToSystemTicks(total_ticks_to_target, s_locals.crtc_state.fractional_ticks); +} - return CRTCTicksToSystemTicks(total_ticks_to_target, m_crtc_state.fractional_ticks); +u16 GPU::GetCRTCActiveStartLine() +{ + return s_locals.crtc_state.vertical_display_start; +} + +u16 GPU::GetCRTCActiveEndLine() +{ + return s_locals.crtc_state.vertical_display_end; } u32 GPU::ReadGPUREAD() { - if (m_blitter_state != BlitterState::ReadingVRAM) - return m_GPUREAD_latch; + if (s_locals.blitter_state != BlitterState::ReadingVRAM) + return s_locals.GPUREAD_latch; // Read two pixels out of VRAM and combine them. Zero fill odd pixel counts. u32 value = 0; for (u32 i = 0; i < 2; i++) { // Read with correct wrap-around behavior. - const u16 read_x = (m_vram_transfer.x + m_vram_transfer.col) % VRAM_WIDTH; - const u16 read_y = (m_vram_transfer.y + m_vram_transfer.row) % VRAM_HEIGHT; + const u16 read_x = (s_locals.vram_transfer.x + s_locals.vram_transfer.col) % VRAM_WIDTH; + const u16 read_y = (s_locals.vram_transfer.y + s_locals.vram_transfer.row) % VRAM_HEIGHT; value |= ZeroExtend32(g_vram[read_y * VRAM_WIDTH + read_x]) << (i * 16); - if (++m_vram_transfer.col == m_vram_transfer.width) + if (++s_locals.vram_transfer.col == s_locals.vram_transfer.width) { - m_vram_transfer.col = 0; + s_locals.vram_transfer.col = 0; - if (++m_vram_transfer.row == m_vram_transfer.height) + if (++s_locals.vram_transfer.row == s_locals.vram_transfer.height) { DEBUG_LOG("End of VRAM->CPU transfer"); - m_vram_transfer = {}; - m_blitter_state = BlitterState::Idle; + s_locals.vram_transfer = {}; + s_locals.blitter_state = BlitterState::Idle; // end of transfer, catch up on any commands which were written (unlikely) ExecuteCommands(); @@ -1496,7 +1885,7 @@ u32 GPU::ReadGPUREAD() } } - m_GPUREAD_latch = value; + s_locals.GPUREAD_latch = value; return value; } @@ -1509,7 +1898,7 @@ void GPU::WriteGP1(u32 value) case static_cast(GP1Command::ResetGPU): { DEBUG_LOG("GP1 reset GPU"); - s_command_tick_event.InvokeEarly(); + s_locals.command_tick_event.InvokeEarly(); SynchronizeCRTC(); SoftReset(); } @@ -1518,21 +1907,21 @@ void GPU::WriteGP1(u32 value) case static_cast(GP1Command::ClearFIFO): { DEBUG_LOG("GP1 clear FIFO"); - s_command_tick_event.InvokeEarly(); + s_locals.command_tick_event.InvokeEarly(); SynchronizeCRTC(); // flush partial writes - if (m_blitter_state == BlitterState::WritingVRAM) + if (s_locals.blitter_state == BlitterState::WritingVRAM) FinishVRAMWrite(); - m_blitter_state = BlitterState::Idle; - m_command_total_words = 0; - m_vram_transfer = {}; - m_fifo.Clear(); - m_blit_buffer.clear(); - m_blit_remaining_words = 0; - m_pending_command_ticks = 0; - s_command_tick_event.Deactivate(); + s_locals.blitter_state = BlitterState::Idle; + s_locals.command_total_words = 0; + s_locals.vram_transfer = {}; + s_locals.fifo.Clear(); + s_locals.blit_buffer.clear(); + s_locals.blit_remaining_words = 0; + s_locals.pending_command_ticks = 0; + s_locals.command_tick_event.Deactivate(); UpdateDMARequest(); UpdateGPUIdle(); } @@ -1541,7 +1930,7 @@ void GPU::WriteGP1(u32 value) case static_cast(GP1Command::AcknowledgeInterrupt): { DEBUG_LOG("Acknowledge interrupt"); - m_GPUSTAT.interrupt_request = false; + s_locals.GPUSTAT.interrupt_request = false; InterruptController::SetLineState(InterruptController::IRQ::GPU, false); } break; @@ -1552,16 +1941,16 @@ void GPU::WriteGP1(u32 value) DEBUG_LOG("Display {}", disable ? "disabled" : "enabled"); SynchronizeCRTC(); - m_GPUSTAT.display_disable = disable; + s_locals.GPUSTAT.display_disable = disable; } break; case static_cast(GP1Command::SetDMADirection): { DEBUG_LOG("DMA direction <- 0x{:02X}", static_cast(param)); - if (m_GPUSTAT.dma_direction != static_cast(param)) + if (s_locals.GPUSTAT.dma_direction != static_cast(param)) { - m_GPUSTAT.dma_direction = static_cast(param); + s_locals.GPUSTAT.dma_direction = static_cast(param); UpdateDMARequest(); } } @@ -1573,10 +1962,10 @@ void GPU::WriteGP1(u32 value) DEBUG_LOG("Display address start <- 0x{:08X}", new_value); System::IncrementInternalFrameNumber(); - if (m_crtc_state.regs.display_address_start != new_value) + if (s_locals.crtc_state.regs.display_address_start != new_value) { SynchronizeCRTC(); - m_crtc_state.regs.display_address_start = new_value; + s_locals.crtc_state.regs.display_address_start = new_value; UpdateCRTCDisplayParameters(); GPUBackend::PushCommand(GPUBackend::NewBufferSwappedCommand()); } @@ -1588,10 +1977,10 @@ void GPU::WriteGP1(u32 value) const u32 new_value = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK; DEBUG_LOG("Horizontal display range <- 0x{:08X}", new_value); - if (m_crtc_state.regs.horizontal_display_range != new_value) + if (s_locals.crtc_state.regs.horizontal_display_range != new_value) { SynchronizeCRTC(); - m_crtc_state.regs.horizontal_display_range = new_value; + s_locals.crtc_state.regs.horizontal_display_range = new_value; UpdateCRTCConfig(); } } @@ -1602,10 +1991,10 @@ void GPU::WriteGP1(u32 value) const u32 new_value = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK; DEBUG_LOG("Vertical display range <- 0x{:08X}", new_value); - if (m_crtc_state.regs.vertical_display_range != new_value) + if (s_locals.crtc_state.regs.vertical_display_range != new_value) { SynchronizeCRTC(); - m_crtc_state.regs.vertical_display_range = new_value; + s_locals.crtc_state.regs.vertical_display_range = new_value; UpdateCRTCConfig(); } } @@ -1614,7 +2003,7 @@ void GPU::WriteGP1(u32 value) case static_cast(GP1Command::SetDisplayMode): { const GP1SetDisplayMode dm{param}; - GPUSTAT new_GPUSTAT{m_GPUSTAT.bits}; + GPUSTATReg new_GPUSTAT{s_locals.GPUSTAT.bits}; new_GPUSTAT.horizontal_resolution_1 = dm.horizontal_resolution_1; new_GPUSTAT.vertical_resolution = dm.vertical_resolution; new_GPUSTAT.pal_mode = dm.pal_mode; @@ -1624,19 +2013,19 @@ void GPU::WriteGP1(u32 value) new_GPUSTAT.reverse_flag = dm.reverse_flag; DEBUG_LOG("Set display mode <- 0x{:08X}", dm.bits); - if (!m_GPUSTAT.vertical_interlace && dm.vertical_interlace && !m_force_progressive_scan) + if (!s_locals.GPUSTAT.vertical_interlace && dm.vertical_interlace && !s_locals.force_progressive_scan) { // bit of a hack, technically we should pull the previous frame in, but this may not exist anymore ClearDisplay(); } - if (m_GPUSTAT.bits != new_GPUSTAT.bits) + if (s_locals.GPUSTAT.bits != new_GPUSTAT.bits) { // Have to be careful when setting this because Synchronize() can modify GPUSTAT. static constexpr u32 SET_MASK = UINT32_C(0b00000000011111110100000000000000); - s_command_tick_event.InvokeEarly(); + s_locals.command_tick_event.InvokeEarly(); SynchronizeCRTC(); - m_GPUSTAT.bits = (m_GPUSTAT.bits & ~SET_MASK) | (new_GPUSTAT.bits & SET_MASK); + s_locals.GPUSTAT.bits = (s_locals.GPUSTAT.bits & ~SET_MASK) | (new_GPUSTAT.bits & SET_MASK); UpdateCRTCConfig(); } } @@ -1644,8 +2033,8 @@ void GPU::WriteGP1(u32 value) case static_cast(GP1Command::SetAllowTextureDisable): { - m_set_texture_disable_mask = ConvertToBoolUnchecked(param & 0x01); - DEBUG_LOG("Set texture disable mask <- {}", m_set_texture_disable_mask ? "allowed" : "ignored"); + s_locals.set_texture_disable_mask = ConvertToBoolUnchecked(param & 0x01); + DEBUG_LOG("Set texture disable mask <- {}", s_locals.set_texture_disable_mask ? "allowed" : "ignored"); } break; @@ -1690,31 +2079,32 @@ void GPU::HandleGetGPUInfoCommand(u32 value) case 0x02: // Get Texture Window { - m_GPUREAD_latch = m_draw_mode.texture_window_value; - DEBUG_LOG("Get texture window => 0x{:08X}", m_GPUREAD_latch); + s_locals.GPUREAD_latch = s_locals.draw_mode.texture_window_value; + DEBUG_LOG("Get texture window => 0x{:08X}", s_locals.GPUREAD_latch); } break; case 0x03: // Get Draw Area Top Left { - m_GPUREAD_latch = (m_drawing_area.left | (m_drawing_area.top << 10)); - DEBUG_LOG("Get drawing area top left: ({}, {}) => 0x{:08X}", m_drawing_area.left, m_drawing_area.top, - m_GPUREAD_latch); + s_locals.GPUREAD_latch = (s_locals.drawing_area.left | (s_locals.drawing_area.top << 10)); + DEBUG_LOG("Get drawing area top left: ({}, {}) => 0x{:08X}", s_locals.drawing_area.left, + s_locals.drawing_area.top, s_locals.GPUREAD_latch); } break; case 0x04: // Get Draw Area Bottom Right { - m_GPUREAD_latch = (m_drawing_area.right | (m_drawing_area.bottom << 10)); - DEBUG_LOG("Get drawing area bottom right: ({}, {}) => 0x{:08X}", m_drawing_area.right, m_drawing_area.bottom, - m_GPUREAD_latch); + s_locals.GPUREAD_latch = (s_locals.drawing_area.right | (s_locals.drawing_area.bottom << 10)); + DEBUG_LOG("Get drawing area bottom right: ({}, {}) => 0x{:08X}", s_locals.drawing_area.right, + s_locals.drawing_area.bottom, s_locals.GPUREAD_latch); } break; case 0x05: // Get Drawing Offset { - m_GPUREAD_latch = (m_drawing_offset.x & 0x7FF) | ((m_drawing_offset.y & 0x7FF) << 11); - DEBUG_LOG("Get drawing offset: ({}, {}) => 0x{:08X}", m_drawing_offset.x, m_drawing_offset.y, m_GPUREAD_latch); + s_locals.GPUREAD_latch = (s_locals.drawing_offset.x & 0x7FF) | ((s_locals.drawing_offset.y & 0x7FF) << 11); + DEBUG_LOG("Get drawing offset: ({}, {}) => 0x{:08X}", s_locals.drawing_offset.x, s_locals.drawing_offset.y, + s_locals.GPUREAD_latch); } break; @@ -1730,12 +2120,13 @@ void GPU::UpdateCLUTIfNeeded(GPUTextureMode texmode, GPUTexturePaletteReg clut) return; const bool needs_8bit = (texmode == GPUTextureMode::Palette8Bit); - if ((clut.bits != m_current_clut_reg_bits) || BoolToUInt8(needs_8bit) > BoolToUInt8(m_current_clut_is_8bit)) + if ((clut.bits != s_locals.current_clut_reg_bits) || + BoolToUInt8(needs_8bit) > BoolToUInt8(s_locals.current_clut_is_8bit)) { DEBUG_LOG("Reloading CLUT from {},{}, {}", clut.GetXBase(), clut.GetYBase(), needs_8bit ? "8-bit" : "4-bit"); AddCommandTicks(needs_8bit ? 256 : 16); - m_current_clut_reg_bits = clut.bits; - m_current_clut_is_8bit = needs_8bit; + s_locals.current_clut_reg_bits = clut.bits; + s_locals.current_clut_is_8bit = needs_8bit; GPUBackendUpdateCLUTCommand* cmd = GPUBackend::NewUpdateCLUTCommand(); cmd->reg.bits = clut.bits; @@ -1746,18 +2137,14 @@ void GPU::UpdateCLUTIfNeeded(GPUTextureMode texmode, GPUTexturePaletteReg clut) void GPU::InvalidateCLUT() { - m_current_clut_reg_bits = std::numeric_limits::max(); // will never match - m_current_clut_is_8bit = false; -} - -bool GPU::IsCLUTValid() const -{ - return (m_current_clut_reg_bits != std::numeric_limits::max()); + s_locals.current_clut_reg_bits = + std::numeric_limits::max(); // will never match + s_locals.current_clut_is_8bit = false; } void GPU::SetClampedDrawingArea() { - m_clamped_drawing_area = GetClampedDrawingArea(m_drawing_area); + s_locals.clamped_drawing_area = GetClampedDrawingArea(s_locals.drawing_area); } GSVector4i GPU::GetClampedDrawingArea(const GPUDrawingArea& drawing_area) @@ -1775,27 +2162,27 @@ GSVector4i GPU::GetClampedDrawingArea(const GPUDrawingArea& drawing_area) void GPU::SetDrawMode(u16 value) { GPUDrawModeReg new_mode_reg{static_cast(value & GPUDrawModeReg::MASK)}; - if (!m_set_texture_disable_mask) + if (!s_locals.set_texture_disable_mask) new_mode_reg.texture_disable = false; - m_draw_mode.mode_reg.bits = new_mode_reg.bits; + s_locals.draw_mode.mode_reg.bits = new_mode_reg.bits; // Bits 0..10 are returned in the GPU status register. - m_GPUSTAT.bits = (m_GPUSTAT.bits & ~(GPUDrawModeReg::GPUSTAT_MASK)) | - (ZeroExtend32(new_mode_reg.bits) & GPUDrawModeReg::GPUSTAT_MASK); - m_GPUSTAT.texture_disable = m_draw_mode.mode_reg.texture_disable; + s_locals.GPUSTAT.bits = (s_locals.GPUSTAT.bits & ~(GPUDrawModeReg::GPUSTAT_MASK)) | + (ZeroExtend32(new_mode_reg.bits) & GPUDrawModeReg::GPUSTAT_MASK); + s_locals.GPUSTAT.texture_disable = s_locals.draw_mode.mode_reg.texture_disable; } void GPU::SetTexturePalette(u16 value) { value &= DrawMode::PALETTE_MASK; - m_draw_mode.palette_reg.bits = value; + s_locals.draw_mode.palette_reg.bits = value; } void GPU::SetTextureWindow(u32 value) { value &= DrawMode::TEXTURE_WINDOW_MASK; - if (m_draw_mode.texture_window_value == value) + if (s_locals.draw_mode.texture_window_value == value) return; const u8 mask_x = Truncate8(value & UINT32_C(0x1F)); @@ -1804,11 +2191,11 @@ void GPU::SetTextureWindow(u32 value) const u8 offset_y = Truncate8((value >> 15) & UINT32_C(0x1F)); DEBUG_LOG("Set texture window {:02X} {:02X} {:02X} {:02X}", mask_x, mask_y, offset_x, offset_y); - m_draw_mode.texture_window.and_x = ~(mask_x * 8); - m_draw_mode.texture_window.and_y = ~(mask_y * 8); - m_draw_mode.texture_window.or_x = (offset_x & mask_x) * 8u; - m_draw_mode.texture_window.or_y = (offset_y & mask_y) * 8u; - m_draw_mode.texture_window_value = value; + s_locals.draw_mode.texture_window.and_x = ~(mask_x * 8); + s_locals.draw_mode.texture_window.and_y = ~(mask_y * 8); + s_locals.draw_mode.texture_window.or_x = (offset_x & mask_x) * 8u; + s_locals.draw_mode.texture_window.or_y = (offset_y & mask_y) * 8u; + s_locals.draw_mode.texture_window_value = value; } static bool IntegerScalePreferWidth(float display_width, float display_height, float pixel_aspect_ratio, @@ -2023,8 +2410,8 @@ void GPU::ClearDisplay() void GPU::UpdateDisplay(bool submit_frame) { const bool interlaced = IsInterlacedDisplayEnabled(); - const u8 interlaced_field = m_crtc_state.interlaced_field; - const bool line_skip = (interlaced && m_GPUSTAT.vertical_resolution); + const u8 interlaced_field = s_locals.crtc_state.interlaced_field; + const bool line_skip = (interlaced && s_locals.GPUSTAT.vertical_resolution); // NOTE: Must be split out, since this can push commands itself (e.g. media capture). GPUBackendFramePresentationParameters frame; @@ -2034,20 +2421,20 @@ void GPU::UpdateDisplay(bool submit_frame) cmd->gpu_busy_pct = g_settings.display_show_gpu_stats ? UpdateOrGetGPUBusyPct() : 0; if (!g_settings.gpu_show_vram) [[likely]] { - cmd->display_width = m_crtc_state.display_width; - cmd->display_height = m_crtc_state.display_height; - cmd->display_origin_left = m_crtc_state.display_origin_left; - cmd->display_origin_top = m_crtc_state.display_origin_top; - cmd->display_vram_left = m_crtc_state.display_vram_left; - cmd->display_vram_top = m_crtc_state.display_vram_top; - cmd->display_vram_width = m_crtc_state.display_vram_width; - cmd->display_vram_height = m_crtc_state.display_vram_height >> BoolToUInt8(interlaced); - cmd->X = m_crtc_state.regs.X; + cmd->display_width = s_locals.crtc_state.display_width; + cmd->display_height = s_locals.crtc_state.display_height; + cmd->display_origin_left = s_locals.crtc_state.display_origin_left; + cmd->display_origin_top = s_locals.crtc_state.display_origin_top; + cmd->display_vram_left = s_locals.crtc_state.display_vram_left; + cmd->display_vram_top = s_locals.crtc_state.display_vram_top; + cmd->display_vram_width = s_locals.crtc_state.display_vram_width; + cmd->display_vram_height = s_locals.crtc_state.display_vram_height >> BoolToUInt8(interlaced); + cmd->X = s_locals.crtc_state.regs.X; cmd->interlaced_display_enabled = interlaced; cmd->interlaced_display_field = ConvertToBoolUnchecked(interlaced_field); cmd->interlaced_display_interleaved = line_skip; - cmd->interleaved_480i_mode = m_GPUSTAT.InInterleaved480iMode(); - cmd->display_24bit = m_GPUSTAT.display_area_color_depth_24; + cmd->interleaved_480i_mode = s_locals.GPUSTAT.InInterleaved480iMode(); + cmd->display_24bit = s_locals.GPUSTAT.display_area_color_depth_24; cmd->display_disabled = IsDisplayDisabled(); cmd->display_pixel_aspect_ratio = ComputePixelAspectRatio(); } @@ -2106,15 +2493,16 @@ void GPU::QueuePresentCurrentFrame() GPUBackend::WaitForOneQueuedFrame(); } -u8 GPU::CalculateAutomaticResolutionScale() const +u8 GPU::CalculateAutomaticResolutionScale() { // Auto scaling. // When the system is starting and all borders crop is enabled, the registers are zero, and // display_height therefore is also zero. Keep the existing resolution until it updates. u32 scale = 1; if (const WindowInfo& main_window_info = VideoThread::GetRenderWindowInfo(); - !main_window_info.IsSurfaceless() && m_crtc_state.display_width > 0 && m_crtc_state.display_height > 0 && - m_crtc_state.display_vram_width > 0 && m_crtc_state.display_vram_height > 0) + !main_window_info.IsSurfaceless() && s_locals.crtc_state.display_width > 0 && + s_locals.crtc_state.display_height > 0 && s_locals.crtc_state.display_vram_width > 0 && + s_locals.crtc_state.display_vram_height > 0) { GSVector4i source_rect, display_rect, draw_rect; CalculateDrawRect(GSVector2i(main_window_info.surface_width, main_window_info.surface_height), GetCRTCVideoSize(), @@ -2127,12 +2515,12 @@ u8 GPU::CalculateAutomaticResolutionScale() const // anamorphic aspect ratio. const s32 draw_width = draw_rect.width(); const s32 draw_height = draw_rect.height(); - scale = static_cast( - std::ceil(std::max(static_cast(draw_width) / static_cast(m_crtc_state.display_vram_width), - static_cast(draw_height) / static_cast(m_crtc_state.display_vram_height)))); + scale = static_cast(std::ceil( + std::max(static_cast(draw_width) / static_cast(s_locals.crtc_state.display_vram_width), + static_cast(draw_height) / static_cast(s_locals.crtc_state.display_vram_height)))); scale = std::min(scale, std::numeric_limits::max()); VERBOSE_LOG("Draw Size = {}x{}, VRAM Size = {}x{}, Preferred Scale = {}", draw_width, draw_height, - m_crtc_state.display_vram_width, m_crtc_state.display_vram_height, scale); + s_locals.crtc_state.display_vram_width, s_locals.crtc_state.display_vram_height, scale); } return Truncate8(scale); @@ -2186,31 +2574,106 @@ bool GPU::DumpVRAMToFile(std::string path, u32 width, u32 height, u32 stride, co return image.SaveToFile(path.c_str(), Image::DEFAULT_SAVE_QUALITY, error); } +static constexpr GPU::GP0CommandHandlerTable s_GP0_command_handler_table = []() constexpr { + GPU::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++) + { + switch (static_cast((i >> 5) & 0x03)) + { + case GPUPrimitive::Polygon: + table[i] = &GPU::HandleRenderPolygonCommand; + break; + case GPUPrimitive::Line: + table[i] = (i & 0x08) ? &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; +}(); + #define CHECK_COMMAND_SIZE(num_words) \ - if (m_fifo.GetSize() < num_words) \ + if (s_locals.fifo.GetSize() < num_words) \ { \ - m_command_total_words = num_words; \ + s_locals.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; } +ALWAYS_INLINE u32 GPU::FifoPop() +{ + return Truncate32(s_locals.fifo.Pop()); +} + +ALWAYS_INLINE u32 GPU::FifoPeek() +{ + return Truncate32(s_locals.fifo.Peek()); +} + +ALWAYS_INLINE u32 GPU::FifoPeek(u32 i) +{ + return Truncate32(s_locals.fifo.Peek(i)); +} + +void GPU::ExecuteCommands() +{ + const bool was_executing_from_event = std::exchange(s_locals.executing_commands, true); + + TryExecuteCommands(); + UpdateDMARequest(); + UpdateGPUIdle(); + + s_locals.executing_commands = was_executing_from_event; + if (!was_executing_from_event) + UpdateCommandTickEvent(); +} + void GPU::TryExecuteCommands() { - while (m_pending_command_ticks <= m_max_run_ahead && !m_fifo.IsEmpty()) + while (s_locals.pending_command_ticks <= s_locals.max_run_ahead && !s_locals.fifo.IsEmpty()) { - switch (m_blitter_state) + switch (s_locals.blitter_state) { case BlitterState::Idle: { const u32 command = FifoPeek(0) >> 24; - if ((this->*s_GP0_command_handler_table[command])()) + if (s_GP0_command_handler_table[command]()) continue; else return; @@ -2218,15 +2681,15 @@ void GPU::TryExecuteCommands() 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); + DebugAssert(s_locals.blit_remaining_words > 0); + const u32 words_to_copy = std::min(s_locals.blit_remaining_words, s_locals.fifo.GetSize()); + s_locals.blit_buffer.reserve(s_locals.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; + s_locals.blit_buffer.push_back(FifoPop()); + s_locals.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) + DEBUG_LOG("VRAM write burst of {} words, {} words remaining", words_to_copy, s_locals.blit_remaining_words); + if (s_locals.blit_remaining_words == 0) FinishVRAMWrite(); continue; @@ -2240,10 +2703,10 @@ void GPU::TryExecuteCommands() case BlitterState::DrawingPolyLine: { - const u32 words_per_vertex = m_render_command.shading_enable ? 2 : 1; + const u32 words_per_vertex = s_locals.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) + s_locals.render_command.shading_enable ? ((static_cast(s_locals.polyline_buffer.size()) & 1u) ^ 1u) : 0u; + for (; terminator_index < s_locals.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 @@ -2251,23 +2714,23 @@ void GPU::TryExecuteCommands() break; } - const bool found_terminator = (terminator_index < m_fifo.GetSize()); - const u32 words_to_copy = std::min(terminator_index, m_fifo.GetSize()); + const bool found_terminator = (terminator_index < s_locals.fifo.GetSize()); + const u32 words_to_copy = std::min(terminator_index, s_locals.fifo.GetSize()); if (words_to_copy > 0) { - m_polyline_buffer.reserve(m_polyline_buffer.size() + words_to_copy); + s_locals.polyline_buffer.reserve(s_locals.polyline_buffer.size() + words_to_copy); for (u32 i = 0; i < words_to_copy; i++) - m_polyline_buffer.push_back(m_fifo.Pop()); + s_locals.polyline_buffer.push_back(s_locals.fifo.Pop()); } DEBUG_LOG("Added {} words to polyline", words_to_copy); if (found_terminator) { // drop terminator - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount()); FinishPolyline(); - m_polyline_buffer.clear(); + s_locals.polyline_buffer.clear(); EndCommand(); continue; } @@ -2277,75 +2740,10 @@ void GPU::TryExecuteCommands() } } -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; + s_locals.blitter_state = BlitterState::Idle; + s_locals.command_total_words = 0; } bool GPU::HandleUnknownGP0Command() @@ -2354,18 +2752,18 @@ bool GPU::HandleUnknownGP0Command() ERROR_LOG("Unimplemented GP0 command 0x{:02X}", command); SmallString dump; - for (u32 i = 0; i < m_fifo.GetSize(); i++) + for (u32 i = 0; i < s_locals.fifo.GetSize(); i++) dump.append_format("{}{:08X}", (i > 0) ? " " : "", FifoPeek(i)); ERROR_LOG("FIFO: {}", dump); - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); EndCommand(); return true; } bool GPU::HandleNOPCommand() { - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); EndCommand(); return true; } @@ -2375,7 +2773,7 @@ bool GPU::HandleClearCacheCommand() DEBUG_LOG("GP0 clear cache"); InvalidateCLUT(); GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand()); - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); AddCommandTicks(1); EndCommand(); return true; @@ -2385,10 +2783,10 @@ bool GPU::HandleInterruptRequestCommand() { DEBUG_LOG("GP0 interrupt request"); - m_GPUSTAT.interrupt_request = true; + s_locals.GPUSTAT.interrupt_request = true; InterruptController::SetLineState(InterruptController::IRQ::GPU, true); - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); AddCommandTicks(1); EndCommand(); return true; @@ -2419,11 +2817,11 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand() 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) + if (s_locals.drawing_area.left != left || s_locals.drawing_area.top != top) { - m_drawing_area.left = left; - m_drawing_area.top = top; - m_drawing_area_changed = true; + s_locals.drawing_area.left = left; + s_locals.drawing_area.top = top; + s_locals.drawing_area_changed = true; SetClampedDrawingArea(); } @@ -2439,11 +2837,11 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand() 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) + if (s_locals.drawing_area.right != right || s_locals.drawing_area.bottom != bottom) { - m_drawing_area.right = right; - m_drawing_area.bottom = bottom; - m_drawing_area_changed = true; + s_locals.drawing_area.right = right; + s_locals.drawing_area.bottom = bottom; + s_locals.drawing_area_changed = true; SetClampedDrawingArea(); } @@ -2458,10 +2856,10 @@ bool GPU::HandleSetDrawingOffsetCommand() 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) + if (s_locals.drawing_offset.x != x || s_locals.drawing_offset.y != y) { - m_drawing_offset.x = x; - m_drawing_offset.y = y; + s_locals.drawing_offset.x = x; + s_locals.drawing_offset.y = y; } AddCommandTicks(1); @@ -2475,9 +2873,9 @@ bool GPU::HandleSetMaskBitCommand() 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)); + s_locals.GPUSTAT.bits = (s_locals.GPUSTAT.bits & ~gpustat_mask) | gpustat_bits; + DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(s_locals.GPUSTAT.set_mask_while_drawing), + BoolToUInt32(s_locals.GPUSTAT.check_mask_before_draw)); AddCommandTicks(1); EndCommand(); @@ -2486,37 +2884,37 @@ bool GPU::HandleSetMaskBitCommand() void GPU::PrepareForDraw() { - if (m_drawing_area_changed) + if (s_locals.drawing_area_changed) { - m_drawing_area_changed = false; + s_locals.drawing_area_changed = false; GPUBackendSetDrawingAreaCommand* cmd = GPUBackend::NewSetDrawingAreaCommand(); - cmd->new_area = m_drawing_area; + cmd->new_area = s_locals.drawing_area; GPUBackend::PushCommand(cmd); } } -void GPU::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const +void GPU::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) { 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->active_line_lsb = ConvertToBoolUnchecked(s_locals.crtc_state.active_line_lsb); + cmd->check_mask_before_draw = s_locals.GPUSTAT.check_mask_before_draw; + cmd->set_mask_while_drawing = s_locals.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->dither_enable = rc.IsDitheringEnabled() && s_locals.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; + cmd->draw_mode.bits = s_locals.draw_mode.mode_reg.bits; + cmd->palette.bits = s_locals.draw_mode.palette_reg.bits; + cmd->window = s_locals.draw_mode.texture_window; } -ALWAYS_INLINE u32 GPU::GetPolyLineVertexCount() const +ALWAYS_INLINE u32 GPU::GetPolyLineVertexCount() { - return (static_cast(m_polyline_buffer.size()) + BoolToUInt32(m_render_command.shading_enable)) >> - BoolToUInt8(m_render_command.shading_enable); + return (static_cast(s_locals.polyline_buffer.size()) + BoolToUInt32(s_locals.render_command.shading_enable)) >> + BoolToUInt8(s_locals.render_command.shading_enable); } ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded, @@ -2526,8 +2924,8 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v // 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); + const GSVector2i clamp_min = GSVector2i::load(&s_locals.clamped_drawing_area.x); + const GSVector2i clamp_max = GSVector2i::load(&s_locals.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); @@ -2535,9 +2933,9 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v 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) + if (semitransparent || s_locals.GPUSTAT.check_mask_before_draw) pixels += (pixels + 1) / 2; - if (m_GPUSTAT.SkipDrawingToActiveField()) + if (s_locals.GPUSTAT.SkipDrawingToActiveField()) pixels /= 2; AddCommandTicks(pixels); @@ -2545,7 +2943,7 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawTriangleTicks(GSVector2i v1, GSVector2i v ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, bool textured, bool semitransparent) { - const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect); + const GSVector4i clamped_rect = s_locals.clamped_drawing_area.rintersect(rect); u32 drawn_width = clamped_rect.width(); u32 drawn_height = clamped_rect.height(); @@ -2553,7 +2951,7 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, boo u32 ticks_per_row = drawn_width; if (textured) { - switch (m_draw_mode.mode_reg.texture_mode) + switch (s_locals.draw_mode.mode_reg.texture_mode) { case GPUTextureMode::Palette4Bit: ticks_per_row += drawn_width; @@ -2589,9 +2987,9 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, boo } } - if (semitransparent || m_GPUSTAT.check_mask_before_draw) + if (semitransparent || s_locals.GPUSTAT.check_mask_before_draw) ticks_per_row += (drawn_width + 1u) / 2u; - if (m_GPUSTAT.SkipDrawingToActiveField()) + if (s_locals.GPUSTAT.SkipDrawingToActiveField()) drawn_height = std::max(drawn_height / 2, 1u); AddCommandTicks(ticks_per_row * drawn_height); @@ -2599,7 +2997,7 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawRectangleTicks(const GSVector4i rect, boo ALWAYS_INLINE_RELEASE void GPU::AddDrawLineTicks(const GSVector4i rect, bool shaded) { - const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area); + const GSVector4i clamped_rect = rect.rintersect(s_locals.clamped_drawing_area); // Needed because we're not multiplying either dimension. if (clamped_rect.rempty()) @@ -2608,7 +3006,7 @@ ALWAYS_INLINE_RELEASE void GPU::AddDrawLineTicks(const GSVector4i rect, bool sha const u32 drawn_width = clamped_rect.width(); u32 drawn_height = clamped_rect.height(); - if (m_GPUSTAT.SkipDrawingToActiveField()) + if (s_locals.GPUSTAT.SkipDrawingToActiveField()) drawn_height = std::max(drawn_height / 2, 1u); AddCommandTicks(std::max(drawn_width, drawn_height)); @@ -2644,13 +3042,13 @@ bool GPU::HandleRenderPolygonCommand() { 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)); + (s_locals.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); + UpdateCLUTIfNeeded(s_locals.draw_mode.mode_reg.texture_mode, s_locals.draw_mode.palette_reg); } - m_render_command.bits = rc.bits; - m_fifo.RemoveOne(); + s_locals.render_command.bits = rc.bits; + s_locals.fifo.RemoveOne(); PrepareForDraw(); @@ -2668,14 +3066,15 @@ bool GPU::HandleRenderPolygonCommand() { 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 u64 maddr_and_pos = s_locals.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->native_x = s_locals.drawing_offset.x + vp.x; + vert->native_y = s_locals.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); + valid_w &= + CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y, + s_locals.drawing_offset.x, s_locals.drawing_offset.y, &vert->x, &vert->y, &vert->w); } cmd->valid_w = valid_w; @@ -2790,10 +3189,10 @@ bool GPU::HandleRenderPolygonCommand() { 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 u64 maddr_and_pos = s_locals.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->x = s_locals.drawing_offset.x + vp.x; + vert->y = s_locals.drawing_offset.y + vp.y; vert->texcoord = textured ? Truncate16(FifoPop()) : 0; } @@ -2887,7 +3286,7 @@ bool GPU::HandleRenderRectangleCommand() if (rc.texture_enable) { SetTexturePalette(Truncate16(FifoPeek(2) >> 16)); - UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg); + UpdateCLUTIfNeeded(s_locals.draw_mode.mode_reg.texture_mode, s_locals.draw_mode.palette_reg); } const TickCount setup_ticks = 16; @@ -2897,8 +3296,8 @@ bool GPU::HandleRenderRectangleCommand() 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(); + s_locals.render_command.bits = rc.bits; + s_locals.fifo.RemoveOne(); PrepareForDraw(); GPUBackendDrawRectangleCommand* cmd = GPUBackend::NewDrawRectangleCommand(); @@ -2906,8 +3305,8 @@ bool GPU::HandleRenderRectangleCommand() 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); + cmd->x = TruncateGPUVertexPosition(s_locals.drawing_offset.x + vp.x); + cmd->y = TruncateGPUVertexPosition(s_locals.drawing_offset.y + vp.y); if (rc.texture_enable) { @@ -2964,8 +3363,8 @@ bool GPU::HandleRenderLineCommand() 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(); + s_locals.render_command.bits = rc.bits; + s_locals.fifo.RemoveOne(); PrepareForDraw(); @@ -2979,15 +3378,16 @@ bool GPU::HandleRenderLineCommand() 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 u64 maddr_and_pos = s_locals.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->native_x = s_locals.drawing_offset.x + vp.x; + vert->native_y = s_locals.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); + valid_w &= + CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y, + s_locals.drawing_offset.x, s_locals.drawing_offset.y, &vert->x, &vert->y, &vert->w); } if (!(cmd->valid_w = valid_w)) { @@ -3018,13 +3418,13 @@ bool GPU::HandleRenderLineCommand() { 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[0].x = s_locals.drawing_offset.x + start_pos.x; + cmd->vertices[0].y = s_locals.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; + cmd->vertices[1].x = s_locals.drawing_offset.x + end_pos.x; + cmd->vertices[1].y = s_locals.drawing_offset.y + end_pos.y; } else { @@ -3032,12 +3432,12 @@ bool GPU::HandleRenderLineCommand() 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; + cmd->vertices[0].x = s_locals.drawing_offset.x + start_pos.x; + cmd->vertices[0].y = s_locals.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; + cmd->vertices[1].x = s_locals.drawing_offset.x + end_pos.x; + cmd->vertices[1].y = s_locals.drawing_offset.y + end_pos.y; } const GSVector2i v0 = GSVector2i::load(&cmd->vertices[0].x); @@ -3074,19 +3474,19 @@ bool GPU::HandleRenderPolyLineCommand() 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(); + s_locals.render_command.bits = rc.bits; + s_locals.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); + s_locals.polyline_buffer.reserve(words_to_pop); for (u32 i = 0; i < words_to_pop; i++) - m_polyline_buffer.push_back(m_fifo.Pop()); + s_locals.polyline_buffer.push_back(s_locals.fifo.Pop()); // polyline goes via a different path through the blit buffer - m_blitter_state = BlitterState::DrawingPolyLine; - m_command_total_words = 0; + s_locals.blitter_state = BlitterState::DrawingPolyLine; + s_locals.command_total_words = 0; return true; } @@ -3100,32 +3500,33 @@ void GPU::FinishPolyline() if (g_settings.gpu_pgxp_enable) { GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand((num_vertices - 1) * 2); - FillDrawCommand(cmd, m_render_command); + FillDrawCommand(cmd, s_locals.render_command); cmd->palette.bits = 0; u32 buffer_pos = 0; u32 out_vertex_count = 0; - const bool shaded = m_render_command.shading_enable; + const bool shaded = s_locals.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 auto read_vertex = [&buffer_pos, &valid_w](GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT dest, + u32 color) { + const u64 maddr_and_pos = s_locals.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.native_x = s_locals.drawing_offset.x + vp.x; + dest.native_y = s_locals.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); + valid_w &= + CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, dest.native_x, dest.native_y, + s_locals.drawing_offset.x, s_locals.drawing_offset.y, &dest.x, &dest.y, &dest.w); }; - read_vertex(start, m_render_command.color_for_first_vertex); + read_vertex(start, s_locals.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); + const u32 color = (shaded ? Truncate32(s_locals.polyline_buffer[buffer_pos++]) : s_locals.render_command.bits) & + UINT32_C(0x00FFFFFF); read_vertex(end, color); const GSVector2i start_pos = GSVector2i::load(&start.native_x); @@ -3138,7 +3539,7 @@ void GPU::FinishPolyline() } else { - AddDrawLineTicks(rect, m_render_command.shading_enable); + AddDrawLineTicks(rect, s_locals.render_command.shading_enable); cmd->vertices[out_vertex_count++] = start; cmd->vertices[out_vertex_count++] = end; @@ -3157,22 +3558,22 @@ void GPU::FinishPolyline() else { GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand((num_vertices - 1) * 2); - FillDrawCommand(cmd, m_render_command); + FillDrawCommand(cmd, s_locals.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); + const GPUVertexPosition start_vp{Truncate32(s_locals.polyline_buffer[buffer_pos++])}; + const GSVector2i draw_offset = GSVector2i::load(&s_locals.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; + u32 start_color = s_locals.render_command.color_for_first_vertex; - const bool shaded = m_render_command.shading_enable; + const bool shaded = s_locals.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 u32 end_color = shaded ? (Truncate32(s_locals.polyline_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF))) : + s_locals.render_command.color_for_first_vertex; + const GPUVertexPosition vp{Truncate32(s_locals.polyline_buffer[buffer_pos++])}; const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset); const GSVector4i rect = @@ -3183,7 +3584,7 @@ void GPU::FinishPolyline() } else { - AddDrawLineTicks(rect, m_render_command.shading_enable); + AddDrawLineTicks(rect, s_locals.render_command.shading_enable); GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count]; out_vertex_count += 2; @@ -3231,7 +3632,7 @@ bool GPU::HandleFillRectangleCommand() cmd->height = static_cast(height); cmd->color = color; cmd->interlaced_rendering = IsInterlacedRenderingEnabled(); - cmd->active_line_lsb = m_crtc_state.active_line_lsb; + cmd->active_line_lsb = s_locals.crtc_state.active_line_lsb; GPUBackend::PushCommand(cmd); } @@ -3243,7 +3644,7 @@ bool GPU::HandleFillRectangleCommand() bool GPU::HandleCopyRectangleCPUToVRAMCommand() { CHECK_COMMAND_SIZE(3); - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); const u32 coords = FifoPop(); const u32 size = FifoPop(); @@ -3269,13 +3670,13 @@ bool GPU::HandleCopyRectangleCPUToVRAMCommand() 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); + s_locals.blitter_state = BlitterState::WritingVRAM; + s_locals.blit_buffer.reserve(num_words); + s_locals.blit_remaining_words = num_words; + s_locals.vram_transfer.x = Truncate16(dst_x); + s_locals.vram_transfer.y = Truncate16(dst_y); + s_locals.vram_transfer.width = Truncate16(copy_width); + s_locals.vram_transfer.height = Truncate16(copy_height); return true; } @@ -3284,83 +3685,86 @@ void GPU::FinishVRAMWrite() if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending()) SynchronizeCRTC(); - if (m_blit_remaining_words == 0) + if (s_locals.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); + ++s_locals.cpu_to_vram_dump_id), + s_locals.vram_transfer.width, s_locals.vram_transfer.height, + sizeof(u16) * s_locals.vram_transfer.width, s_locals.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); + UpdateVRAM(s_locals.vram_transfer.x, s_locals.vram_transfer.y, s_locals.vram_transfer.width, + s_locals.vram_transfer.height, s_locals.blit_buffer.data(), s_locals.GPUSTAT.set_mask_while_drawing, + s_locals.GPUSTAT.check_mask_before_draw); } else { - const u32 num_pixels = ZeroExtend32(m_vram_transfer.width) * ZeroExtend32(m_vram_transfer.height); + const u32 num_pixels = ZeroExtend32(s_locals.vram_transfer.width) * ZeroExtend32(s_locals.vram_transfer.height); const u32 num_words = (num_pixels + 1) / 2; - const u32 transferred_words = num_words - m_blit_remaining_words; + const u32 transferred_words = num_words - s_locals.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; + const u32 transferred_full_rows = transferred_pixels / s_locals.vram_transfer.width; + const u32 transferred_width_last_row = transferred_pixels % s_locals.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); + s_locals.blit_remaining_words, num_words, transferred_full_rows, transferred_width_last_row); - const u8* blit_ptr = reinterpret_cast(m_blit_buffer.data()); + const u8* blit_ptr = reinterpret_cast(s_locals.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); + UpdateVRAM(s_locals.vram_transfer.x, s_locals.vram_transfer.y, s_locals.vram_transfer.width, + static_cast(transferred_full_rows), blit_ptr, s_locals.GPUSTAT.set_mask_while_drawing, + s_locals.GPUSTAT.check_mask_before_draw); + blit_ptr += (ZeroExtend32(s_locals.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); + UpdateVRAM(s_locals.vram_transfer.x, static_cast(s_locals.vram_transfer.y + transferred_full_rows), + static_cast(transferred_width_last_row), 1, blit_ptr, s_locals.GPUSTAT.set_mask_while_drawing, + s_locals.GPUSTAT.check_mask_before_draw); } } - m_blit_buffer.clear(); - m_vram_transfer = {}; - m_blitter_state = BlitterState::Idle; + s_locals.blit_buffer.clear(); + s_locals.vram_transfer = {}; + s_locals.blitter_state = BlitterState::Idle; } bool GPU::HandleCopyRectangleVRAMToCPUCommand() { CHECK_COMMAND_SIZE(3); - m_fifo.RemoveOne(); + s_locals.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; + s_locals.vram_transfer.x = Truncate16(FifoPeek() & VRAM_WIDTH_MASK); + s_locals.vram_transfer.y = Truncate16((FifoPop() >> 16) & VRAM_HEIGHT_MASK); + s_locals.vram_transfer.width = ((Truncate16(FifoPeek()) - 1) & VRAM_WIDTH_MASK) + 1; + s_locals.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); + DEBUG_LOG("Copy rectangle from VRAM to CPU offset=({},{}), size=({},{})", s_locals.vram_transfer.x, + s_locals.vram_transfer.y, s_locals.vram_transfer.width, s_locals.vram_transfer.height); + DebugAssert(s_locals.vram_transfer.col == 0 && s_locals.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); + ReadVRAM(s_locals.vram_transfer.x, s_locals.vram_transfer.y, s_locals.vram_transfer.width, + s_locals.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); + ++s_locals.vram_to_cpu_dump_id), + s_locals.vram_transfer.width, s_locals.vram_transfer.height, sizeof(u16) * VRAM_WIDTH, + &g_vram[s_locals.vram_transfer.y * VRAM_WIDTH + s_locals.vram_transfer.x], true); } // switch to pixel-by-pixel read state - m_blitter_state = BlitterState::ReadingVRAM; - m_command_total_words = 0; + s_locals.blitter_state = BlitterState::ReadingVRAM; + s_locals.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); + if (s_locals.gpu_dump) [[unlikely]] + s_locals.gpu_dump->WriteDiscardVRAMRead(s_locals.vram_transfer.width, s_locals.vram_transfer.height); return true; } @@ -3368,7 +3772,7 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand() bool GPU::HandleCopyRectangleVRAMToVRAMCommand() { CHECK_COMMAND_SIZE(4); - m_fifo.RemoveOne(); + s_locals.fifo.RemoveOne(); const u32 src_x = FifoPeek() & VRAM_WIDTH_MASK; const u32 src_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK; @@ -3382,7 +3786,7 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand() // 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); + width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !s_locals.GPUSTAT.set_mask_while_drawing); if (!skip_copy) { GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand(); @@ -3392,8 +3796,8 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand() 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; + cmd->check_mask_before_draw = s_locals.GPUSTAT.check_mask_before_draw; + cmd->set_mask_while_drawing = s_locals.GPUSTAT.set_mask_while_drawing; GPUBackend::PushCommand(cmd); } @@ -3409,32 +3813,32 @@ void GPU::DrawDebugStateWindow(float scale) static constexpr std::array state_strings = { {"Idle", "Reading VRAM", "Writing VRAM", "Drawing Polyline"}}; - ImGui::Text("State: %s", state_strings[static_cast(m_blitter_state)]); - ImGui::Text("Dither: %s", m_GPUSTAT.dither_enable ? "Enabled" : "Disabled"); - ImGui::Text("Draw To Displayed Field: %s", m_GPUSTAT.draw_to_displayed_field ? "Enabled" : "Disabled"); - ImGui::Text("Draw Set Mask Bit: %s", m_GPUSTAT.set_mask_while_drawing ? "Yes" : "No"); - ImGui::Text("Draw To Masked Pixels: %s", m_GPUSTAT.check_mask_before_draw ? "Yes" : "No"); - ImGui::Text("Reverse Flag: %s", m_GPUSTAT.reverse_flag ? "Yes" : "No"); - ImGui::Text("Texture Disable: %s", m_GPUSTAT.texture_disable ? "Yes" : "No"); - ImGui::Text("PAL Mode: %s", m_GPUSTAT.pal_mode ? "Yes" : "No"); - ImGui::Text("Interrupt Request: %s", m_GPUSTAT.interrupt_request ? "Yes" : "No"); - ImGui::Text("DMA Request: %s", m_GPUSTAT.dma_data_request ? "Yes" : "No"); + ImGui::Text("State: %s", state_strings[static_cast(s_locals.blitter_state)]); + ImGui::Text("Dither: %s", s_locals.GPUSTAT.dither_enable ? "Enabled" : "Disabled"); + ImGui::Text("Draw To Displayed Field: %s", s_locals.GPUSTAT.draw_to_displayed_field ? "Enabled" : "Disabled"); + ImGui::Text("Draw Set Mask Bit: %s", s_locals.GPUSTAT.set_mask_while_drawing ? "Yes" : "No"); + ImGui::Text("Draw To Masked Pixels: %s", s_locals.GPUSTAT.check_mask_before_draw ? "Yes" : "No"); + ImGui::Text("Reverse Flag: %s", s_locals.GPUSTAT.reverse_flag ? "Yes" : "No"); + ImGui::Text("Texture Disable: %s", s_locals.GPUSTAT.texture_disable ? "Yes" : "No"); + ImGui::Text("PAL Mode: %s", s_locals.GPUSTAT.pal_mode ? "Yes" : "No"); + ImGui::Text("Interrupt Request: %s", s_locals.GPUSTAT.interrupt_request ? "Yes" : "No"); + ImGui::Text("DMA Request: %s", s_locals.GPUSTAT.dma_data_request ? "Yes" : "No"); } if (ImGui::CollapsingHeader("CRTC", ImGuiTreeNodeFlags_DefaultOpen)) { - const auto& cs = m_crtc_state; - ImGui::Text("Clock: %s", (m_console_is_pal ? (m_GPUSTAT.pal_mode ? "PAL-on-PAL" : "NTSC-on-PAL") : - (m_GPUSTAT.pal_mode ? "PAL-on-NTSC" : "NTSC-on-NTSC"))); + const auto& cs = s_locals.crtc_state; + ImGui::Text("Clock: %s", (s_locals.console_is_pal ? (s_locals.GPUSTAT.pal_mode ? "PAL-on-PAL" : "NTSC-on-PAL") : + (s_locals.GPUSTAT.pal_mode ? "PAL-on-NTSC" : "NTSC-on-NTSC"))); ImGui::Text("Horizontal Frequency: %.3f KHz", ComputeHorizontalFrequency() / 1000.0f); ImGui::Text("Vertical Frequency: %.3f Hz", ComputeVerticalFrequency()); ImGui::Text("Dot Clock Divider: %u", cs.dot_clock_divider); - ImGui::Text("Vertical Interlace: %s (%s field)", m_GPUSTAT.vertical_interlace ? "Yes" : "No", + ImGui::Text("Vertical Interlace: %s (%s field)", s_locals.GPUSTAT.vertical_interlace ? "Yes" : "No", cs.interlaced_field ? "odd" : "even"); ImGui::Text("Current Scanline: %u (tick %u)", cs.current_scanline, cs.current_tick_in_scanline); - ImGui::Text("Display Disable: %s", m_GPUSTAT.display_disable ? "Yes" : "No"); + ImGui::Text("Display Disable: %s", s_locals.GPUSTAT.display_disable ? "Yes" : "No"); ImGui::Text("Displaying Odd Lines: %s", cs.active_line_lsb ? "Yes" : "No"); - ImGui::Text("Color Depth: %u-bit", m_GPUSTAT.display_area_color_depth_24 ? 24 : 15); + ImGui::Text("Color Depth: %u-bit", s_locals.GPUSTAT.display_area_color_depth_24 ? 24 : 15); ImGui::Text("Start Offset in VRAM: (%u, %u)", cs.regs.X.GetValue(), cs.regs.Y.GetValue()); ImGui::Text("Display Total: %u (%u) horizontal, %u vertical", cs.horizontal_total, cs.horizontal_total / cs.dot_clock_divider, cs.vertical_total); @@ -3458,9 +3862,14 @@ void GPU::DrawDebugStateWindow(float scale) } } +GPUDump::Recorder* GPU::GetGPUDump() +{ + return s_locals.gpu_dump.get(); +} + bool GPU::StartRecordingGPUDump(const char* path, u32 num_frames /* = 1 */) { - if (m_gpu_dump) + if (s_locals.gpu_dump) StopRecordingGPUDump(); // if we're not dumping forever, compute the frame count based on the internal fps @@ -3477,8 +3886,8 @@ bool GPU::StartRecordingGPUDump(const char* path, u32 num_frames /* = 1 */) std::string osd_key = fmt::format("GPUDump_{}", Path::GetFileName(path)); Error error; - m_gpu_dump = GPUDump::Recorder::Create(path, System::GetGameSerial(), num_frames, &error); - if (!m_gpu_dump) + s_locals.gpu_dump = GPUDump::Recorder::Create(path, System::GetGameSerial(), num_frames, &error); + if (!s_locals.gpu_dump) { Host::AddIconOSDMessage( OSDMessageType::Error, std::move(osd_key), ICON_EMOJI_CAMERA_WITH_FLASH, @@ -3501,34 +3910,34 @@ bool GPU::StartRecordingGPUDump(const char* path, u32 num_frames /* = 1 */) void GPU::StopRecordingGPUDump() { - if (!m_gpu_dump) + if (!s_locals.gpu_dump) return; Error error; - if (!m_gpu_dump->Close(&error)) + if (!s_locals.gpu_dump->Close(&error)) { Host::AddIconOSDMessage( OSDMessageType::Error, "GPUDump", ICON_EMOJI_CAMERA_WITH_FLASH, fmt::format("{}\n{}", TRANSLATE_SV("GPU", "Failed to close GPU trace:"), error.GetDescription())); - m_gpu_dump.reset(); + s_locals.gpu_dump.reset(); } // Are we compressing the dump? const GPUDumpCompressionMode compress_mode = Settings::ParseGPUDumpCompressionMode(Core::GetTinyStringSettingValue("GPU", "DumpCompressionMode")) .value_or(Settings::DEFAULT_GPU_DUMP_COMPRESSION_MODE); - std::string osd_key = fmt::format("GPUDump_{}", Path::GetFileName(m_gpu_dump->GetPath())); + std::string osd_key = fmt::format("GPUDump_{}", Path::GetFileName(s_locals.gpu_dump->GetPath())); if (compress_mode == GPUDumpCompressionMode::Disabled) { Host::AddIconOSDMessage( OSDMessageType::Info, "GPUDump", ICON_EMOJI_CAMERA_WITH_FLASH, - fmt::format(TRANSLATE_FS("GPU", "Saved GPU trace to '{}'."), Path::GetFileName(m_gpu_dump->GetPath()))); - m_gpu_dump.reset(); + fmt::format(TRANSLATE_FS("GPU", "Saved GPU trace to '{}'."), Path::GetFileName(s_locals.gpu_dump->GetPath()))); + s_locals.gpu_dump.reset(); return; } - std::string source_path = m_gpu_dump->GetPath(); - m_gpu_dump.reset(); + std::string source_path = s_locals.gpu_dump->GetPath(); + s_locals.gpu_dump.reset(); Host::AddIconOSDMessage( OSDMessageType::Persistent, osd_key, ICON_EMOJI_CAMERA_WITH_FLASH, @@ -3553,53 +3962,53 @@ void GPU::StopRecordingGPUDump() }); } -void GPU::WriteCurrentVideoModeToDump(GPUDump::Recorder* dump) const +void GPU::WriteCurrentVideoModeToDump(GPUDump::Recorder* dump) { - dump->WriteGP1Command(GP1Command::SetDisplayDisable, BoolToUInt32(m_GPUSTAT.display_disable)); - dump->WriteGP1Command(GP1Command::SetDisplayStartAddress, m_crtc_state.regs.display_address_start); - dump->WriteGP1Command(GP1Command::SetHorizontalDisplayRange, m_crtc_state.regs.horizontal_display_range); - dump->WriteGP1Command(GP1Command::SetVerticalDisplayRange, m_crtc_state.regs.vertical_display_range); - dump->WriteGP1Command(GP1Command::SetAllowTextureDisable, BoolToUInt32(m_set_texture_disable_mask)); + dump->WriteGP1Command(GP1Command::SetDisplayDisable, BoolToUInt32(s_locals.GPUSTAT.display_disable)); + dump->WriteGP1Command(GP1Command::SetDisplayStartAddress, s_locals.crtc_state.regs.display_address_start); + dump->WriteGP1Command(GP1Command::SetHorizontalDisplayRange, s_locals.crtc_state.regs.horizontal_display_range); + dump->WriteGP1Command(GP1Command::SetVerticalDisplayRange, s_locals.crtc_state.regs.vertical_display_range); + dump->WriteGP1Command(GP1Command::SetAllowTextureDisable, BoolToUInt32(s_locals.set_texture_disable_mask)); // display mode GP1SetDisplayMode dispmode = {}; - dispmode.horizontal_resolution_1 = m_GPUSTAT.horizontal_resolution_1.GetValue(); - dispmode.vertical_resolution = m_GPUSTAT.vertical_resolution.GetValue(); - dispmode.pal_mode = m_GPUSTAT.pal_mode.GetValue(); - dispmode.display_area_color_depth = m_GPUSTAT.display_area_color_depth_24.GetValue(); - dispmode.vertical_interlace = m_GPUSTAT.vertical_interlace.GetValue(); - dispmode.horizontal_resolution_2 = m_GPUSTAT.horizontal_resolution_2.GetValue(); - dispmode.reverse_flag = m_GPUSTAT.reverse_flag.GetValue(); + dispmode.horizontal_resolution_1 = s_locals.GPUSTAT.horizontal_resolution_1.GetValue(); + dispmode.vertical_resolution = s_locals.GPUSTAT.vertical_resolution.GetValue(); + dispmode.pal_mode = s_locals.GPUSTAT.pal_mode.GetValue(); + dispmode.display_area_color_depth = s_locals.GPUSTAT.display_area_color_depth_24.GetValue(); + dispmode.vertical_interlace = s_locals.GPUSTAT.vertical_interlace.GetValue(); + dispmode.horizontal_resolution_2 = s_locals.GPUSTAT.horizontal_resolution_2.GetValue(); + dispmode.reverse_flag = s_locals.GPUSTAT.reverse_flag.GetValue(); dump->WriteGP1Command(GP1Command::SetDisplayMode, dispmode.bits); // texture window/texture page - dump->WriteGP0Packet((0xE1u << 24) | ZeroExtend32(m_draw_mode.mode_reg.bits)); - dump->WriteGP0Packet((0xE2u << 24) | m_draw_mode.texture_window_value); + dump->WriteGP0Packet((0xE1u << 24) | ZeroExtend32(s_locals.draw_mode.mode_reg.bits)); + dump->WriteGP0Packet((0xE2u << 24) | s_locals.draw_mode.texture_window_value); // drawing area - dump->WriteGP0Packet((0xE3u << 24) | static_cast(m_drawing_area.left) | - (static_cast(m_drawing_area.top) << 10)); - dump->WriteGP0Packet((0xE4u << 24) | static_cast(m_drawing_area.right) | - (static_cast(m_drawing_area.bottom) << 10)); + dump->WriteGP0Packet((0xE3u << 24) | static_cast(s_locals.drawing_area.left) | + (static_cast(s_locals.drawing_area.top) << 10)); + dump->WriteGP0Packet((0xE4u << 24) | static_cast(s_locals.drawing_area.right) | + (static_cast(s_locals.drawing_area.bottom) << 10)); // drawing offset - dump->WriteGP0Packet((0xE5u << 24) | (static_cast(m_drawing_offset.x) & 0x7FFu) | - ((static_cast(m_drawing_offset.y) & 0x7FFu) << 11)); + dump->WriteGP0Packet((0xE5u << 24) | (static_cast(s_locals.drawing_offset.x) & 0x7FFu) | + ((static_cast(s_locals.drawing_offset.y) & 0x7FFu) << 11)); // mask bit - dump->WriteGP0Packet((0xE6u << 24) | BoolToUInt32(m_GPUSTAT.set_mask_while_drawing) | - (BoolToUInt32(m_GPUSTAT.check_mask_before_draw) << 1)); + dump->WriteGP0Packet((0xE6u << 24) | BoolToUInt32(s_locals.GPUSTAT.set_mask_while_drawing) | + (BoolToUInt32(s_locals.GPUSTAT.check_mask_before_draw) << 1)); } void GPU::ProcessGPUDumpPacket(GPUDump::PacketType type, const std::span data) { - const auto execute_all_commands = [this]() { + const auto execute_all_commands = []() { do { - m_pending_command_ticks = 0; - s_command_tick_event.Deactivate(); + s_locals.pending_command_ticks = 0; + s_locals.command_tick_event.Deactivate(); ExecuteCommands(); - } while (m_pending_command_ticks > 0); + } while (s_locals.pending_command_ticks > 0); }; switch (type) @@ -3626,7 +4035,7 @@ void GPU::ProcessGPUDumpPacket(GPUDump::PacketType type, const std::span(data.size() - current_word)); + const u32 block_size = std::min(s_locals.fifo.GetSpace(), static_cast(data.size() - current_word)); if (block_size == 0) { ERROR_LOG("FIFO overflow while processing dump packet of {} words", data.size()); @@ -3634,7 +4043,7 @@ void GPU::ProcessGPUDumpPacket(GPUDump::PacketType type, const std::span(m_crtc_state.horizontal_total) * static_cast(m_crtc_state.vertical_total); + const TickCount crtc_ticks_per_frame = static_cast(s_locals.crtc_state.horizontal_total) * + static_cast(s_locals.crtc_state.vertical_total); const TickCount system_ticks_per_frame = - CRTCTicksToSystemTicks(crtc_ticks_per_frame, m_crtc_state.fractional_ticks); - SystemTicksToCRTCTicks(system_ticks_per_frame, &m_crtc_state.fractional_ticks); + CRTCTicksToSystemTicks(crtc_ticks_per_frame, s_locals.crtc_state.fractional_ticks); + SystemTicksToCRTCTicks(system_ticks_per_frame, &s_locals.crtc_state.fractional_ticks); TimingEvents::SetGlobalTickCounter(TimingEvents::GetGlobalTickCounter() + static_cast(system_ticks_per_frame)); System::IncrementFrameNumber(); diff --git a/src/core/gpu.h b/src/core/gpu.h index 975f8d5d3..671fffbcf 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -6,502 +6,139 @@ #include "gpu_types.h" #include "types.h" -#include "common/bitfield.h" -#include "common/fifo_queue.h" #include "common/gsvector.h" -#include "common/types.h" #include -#include -#include -#include #include #include -#include -#include +#include class Error; -class Image; -class SmallStringBase; class StateWrapper; -class GPUDevice; -class GPUTexture; -class GPUPipeline; -class MediaCapture; - namespace GPUDump { enum class PacketType : u8; class Recorder; -class Player; } // namespace GPUDump -class GPUBackend; struct Settings; namespace System { struct MemorySaveState; } -struct GPUBackendCommand; -struct GPUBackendDrawCommand; - -class GPU final -{ -public: - enum class BlitterState : u8 - { - Idle, - ReadingVRAM, - WritingVRAM, - DrawingPolyLine - }; - - enum : u32 - { - MAX_FIFO_SIZE = 4096, - DOT_TIMER_INDEX = 0, - HBLANK_TIMER_INDEX = 1, - MAX_RESOLUTION_SCALE = 32, - DRAWING_AREA_COORD_MASK = 1023, - }; - - enum : u16 - { - NTSC_TICKS_PER_LINE = 3413, - NTSC_TOTAL_LINES = 263, - PAL_TICKS_PER_LINE = 3406, - PAL_TOTAL_LINES = 314, - }; - - enum : u16 - { - NTSC_HORIZONTAL_ACTIVE_START = 488, - NTSC_HORIZONTAL_ACTIVE_END = 3288, - NTSC_VERTICAL_ACTIVE_START = 16, - NTSC_VERTICAL_ACTIVE_END = 256, - NTSC_OVERSCAN_HORIZONTAL_ACTIVE_START = 608, - NTSC_OVERSCAN_HORIZONTAL_ACTIVE_END = 3168, - NTSC_OVERSCAN_VERTICAL_ACTIVE_START = 24, - NTSC_OVERSCAN_VERTICAL_ACTIVE_END = 248, - PAL_HORIZONTAL_ACTIVE_START = 488, - PAL_HORIZONTAL_ACTIVE_END = 3300, - PAL_VERTICAL_ACTIVE_START = 20, - PAL_VERTICAL_ACTIVE_END = 308, - PAL_OVERSCAN_HORIZONTAL_ACTIVE_START = 628, - PAL_OVERSCAN_HORIZONTAL_ACTIVE_END = 3188, - PAL_OVERSCAN_VERTICAL_ACTIVE_START = 30, - PAL_OVERSCAN_VERTICAL_ACTIVE_END = 298, - }; - - // Base class constructor. - GPU(); - ~GPU(); - - void Initialize(); - void Shutdown(); - void Reset(bool clear_vram); - bool DoState(StateWrapper& sw); - void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss); - - // Render statistics debug window. - void DrawDebugStateWindow(float scale); - - void CPUClockChanged(); - - // MMIO access - u32 ReadRegister(u32 offset); - void WriteRegister(u32 offset, u32 value); - - // DMA access - void DMARead(u32* words, u32 word_count); - - ALWAYS_INLINE bool BeginDMAWrite() const - { - return (m_GPUSTAT.dma_direction == GPUDMADirection::CPUtoGP0 || m_GPUSTAT.dma_direction == GPUDMADirection::FIFO); - } - ALWAYS_INLINE void DMAWrite(u32 address, u32 value) - { - m_fifo.Push((ZeroExtend64(address) << 32) | ZeroExtend64(value)); - } - void EndDMAWrite(); - - /// Writing to GPU dump. - GPUDump::Recorder* GetGPUDump() const { return m_gpu_dump.get(); } - bool StartRecordingGPUDump(const char* path, u32 num_frames = 1); - void StopRecordingGPUDump(); - void WriteCurrentVideoModeToDump(GPUDump::Recorder* dump) const; - void ProcessGPUDumpPacket(GPUDump::PacketType type, const std::span data); - - /// Returns true if no data is being sent from VRAM to the DAC or that no portion of VRAM would be visible on screen. - ALWAYS_INLINE bool IsDisplayDisabled() const - { - return m_GPUSTAT.display_disable || m_crtc_state.display_vram_width == 0 || m_crtc_state.display_vram_height == 0; - } - - /// Returns true if scanout should be interlaced. - ALWAYS_INLINE bool IsInterlacedDisplayEnabled() const - { - return (!m_force_progressive_scan && m_GPUSTAT.vertical_interlace); - } - - /// Returns true if scanout is forced to progressive. - ALWAYS_INLINE bool IsProgressiveDisplayScanForced() const - { - return (m_force_progressive_scan && m_GPUSTAT.vertical_interlace); - } - - /// Returns true if interlaced rendering is enabled and force progressive scan is disabled. - ALWAYS_INLINE bool IsInterlacedRenderingEnabled() const - { - return (!m_force_progressive_scan && m_GPUSTAT.SkipDrawingToActiveField()); - } - - /// Returns true if we're in PAL mode, otherwise false if NTSC. - ALWAYS_INLINE bool IsInPALMode() const { return m_GPUSTAT.pal_mode; } - - /// Returns the number of pending GPU ticks. - TickCount GetPendingCRTCTicks() const; - TickCount GetPendingCommandTicks() const; - TickCount GetRemainingCommandTicks() const; - - /// Returns true if enough ticks have passed for the raster to be on the next line. - bool IsCRTCScanlinePending() const; - - /// Returns true if a raster scanline or command execution is pending. - bool IsCommandCompletionPending() const; - - /// Synchronizes the CRTC, updating the hblank timer. - void SynchronizeCRTC(); - - /// Recompile shaders/recreate framebuffers when needed. - void UpdateSettings(const Settings& old_settings); - - /// Returns the full display resolution of the GPU, including padding. - std::tuple GetFullDisplayResolution() const; - - /// Computes clamped drawing area. - static GSVector4i GetClampedDrawingArea(const GPUDrawingArea& drawing_area); - - float ComputeHorizontalFrequency() const; - float ComputeVerticalFrequency() const; - float ComputePixelAspectRatio() const; - - /// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve - /// the original pixel aspect ratio regardless of how much cropping has been applied. - float ComputeAspectRatioCorrection() const; - - /// Applies the pixel aspect ratio to a given size, preserving the larger dimension. - static GSVector2 CalculateRenderWindowSize(DisplayFineCropMode mode, std::span amount, - float pixel_aspect_ratio, const GSVector2 video_size, - const GSVector2 source_size, const GSVector2 window_size); - - // Converts window coordinates into horizontal ticks and scanlines. Returns -1 if out of range. Used for lightguns. - GSVector2 ConvertScreenCoordinatesToDisplayCoordinates(GSVector2 window_pos) const; - bool ConvertDisplayCoordinatesToBeamTicksAndLines(const GSVector2& display_pos, float x_scale, u32* out_tick, - u32* out_line) const; - - // Returns the current beam position. - void GetBeamPosition(u32* out_ticks, u32* out_line); - - // Returns the number of system clock ticks until the specified tick/line. - TickCount GetSystemTicksUntilTicksAndLine(u32 ticks, u32 line); - - // Returns the number of visible lines. - ALWAYS_INLINE u16 GetCRTCActiveStartLine() const { return m_crtc_state.vertical_display_start; } - ALWAYS_INLINE u16 GetCRTCActiveEndLine() const { return m_crtc_state.vertical_display_end; } - - // Returns the video clock frequency. - TickCount GetCRTCFrequency() const; - - // Video output access. - GSVector2i GetCRTCVideoSize() const; - GSVector4i GetCRTCVideoActiveRect() const; - GSVector4i GetCRTCVRAMSourceRect() const; - - // Ticks for hblank/vblank. - void CRTCTickEvent(TickCount ticks); - void CommandTickEvent(TickCount ticks); - void FrameDoneEvent(TickCount ticks); - - // Dumps raw VRAM to a file. - bool DumpVRAMToFile(std::string path, Error* error); - - // Kicks the current frame to the backend for display. - void UpdateDisplay(bool submit_frame); - - // Queues the current frame for presentation. Should only be used with runahead. - void QueuePresentCurrentFrame(); +namespace GPU { + +/// The maximum resolution scale factor that can be applied to rendering. +inline constexpr u32 MAX_RESOLUTION_SCALE = 32; + +void Initialize(); +void Shutdown(); +void Reset(bool clear_vram); +bool DoState(StateWrapper& sw); +void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss); + +// Render statistics debug window. +void DrawDebugStateWindow(float scale); + +void CPUClockChanged(); + +// MMIO access +u32 ReadRegister(u32 offset); +void WriteRegister(u32 offset, u32 value); + +// DMA access +void DMARead(u32* words, u32 word_count); +bool BeginDMAWrite(); +void DMAWrite(u32 address, u32 value); +void EndDMAWrite(); + +/// Writing to GPU dump. +GPUDump::Recorder* GetGPUDump(); +bool StartRecordingGPUDump(const char* path, u32 num_frames = 1); +void StopRecordingGPUDump(); +void WriteCurrentVideoModeToDump(GPUDump::Recorder* dump); +void ProcessGPUDumpPacket(GPUDump::PacketType type, const std::span data); + +/// Returns true if scanout should be interlaced. +bool IsInterlacedDisplayEnabled(); + +/// Returns true if scanout is forced to progressive. +bool IsProgressiveDisplayScanForced(); + +/// Returns true if we're in PAL mode, otherwise false if NTSC. +bool IsInPALMode(); + +/// Returns true if enough ticks have passed for the raster to be on the next line. +bool IsCRTCScanlinePending(); + +/// Synchronizes the CRTC, updating the hblank timer. +void SynchronizeCRTC(); + +/// Recompile shaders/recreate framebuffers when needed. +void UpdateSettings(const Settings& old_settings); + +/// Returns the full display resolution of the GPU, including padding. +std::pair GetFullDisplayResolution(); + +/// Computes clamped drawing area. +GSVector4i GetClampedDrawingArea(const GPUDrawingArea& drawing_area); + +/// Computes the pixel aspect ratio based on the current display mode and settings. +float ComputePixelAspectRatio(); + +/// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve +/// the original pixel aspect ratio regardless of how much cropping has been applied. +float ComputeAspectRatioCorrection(); + +/// Applies the pixel aspect ratio to a given size, preserving the larger dimension. +GSVector2 CalculateRenderWindowSize(DisplayFineCropMode mode, std::span amount, float pixel_aspect_ratio, + const GSVector2 video_size, const GSVector2 source_size, + const GSVector2 window_size); + +// Converts window coordinates into horizontal ticks and scanlines. Returns -1 if out of range. Used for lightguns. +GSVector2 ConvertScreenCoordinatesToDisplayCoordinates(GSVector2 window_pos); +bool ConvertDisplayCoordinatesToBeamTicksAndLines(const GSVector2& display_pos, float x_scale, u32* out_tick, + u32* out_line); + +// Returns the current beam position. +void GetBeamPosition(u32* out_ticks, u32* out_line); + +// Returns the number of system clock ticks until the specified tick/line. +TickCount GetSystemTicksUntilTicksAndLine(u32 ticks, u32 line); + +// Returns the number of visible lines. +u16 GetCRTCActiveStartLine(); +u16 GetCRTCActiveEndLine(); + +// Returns the video clock frequency. +TickCount GetCRTCFrequency(); + +// Video output access. +GSVector2i GetCRTCVideoSize(); +GSVector4i GetCRTCVideoActiveRect(); +GSVector4i GetCRTCVRAMSourceRect(); + +// Dumps raw VRAM to a file. +bool DumpVRAMToFile(std::string path, Error* error); + +// Kicks the current frame to the backend for display. +void UpdateDisplay(bool submit_frame); - /// Computes the effective resolution scale when it is set to automatic. - u8 CalculateAutomaticResolutionScale() const; - - /// Helper function for computing the draw rectangle in a larger window. - static void CalculateDrawRect(const GSVector2i& window_size, const GSVector2i& video_size, - const GSVector4i& video_active_rect, const GSVector4i& source_rect, - DisplayRotation rotation, DisplayAlignment alignment, float pixel_aspect_ratio, - bool integer_scale, DisplayFineCropMode fine_crop, - const std::span& fine_crop_amount, GSVector4i* out_source_rect, - GSVector4i* out_display_rect, GSVector4i* out_draw_rect, - GSVector4* out_crop_amount = nullptr); +// Queues the current frame for presentation. Should only be used with runahead. +void QueuePresentCurrentFrame(); -private: - TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const; - TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const; +/// Computes the effective resolution scale when it is set to automatic. +u8 CalculateAutomaticResolutionScale(); - // The GPU internally appears to run at 2x the system clock. - ALWAYS_INLINE static constexpr TickCount GPUTicksToSystemTicks(TickCount gpu_ticks) - { - return std::max((gpu_ticks + 1) >> 1, 1); - } - ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; } +/// Helper function for computing the draw rectangle in a larger window. +void CalculateDrawRect(const GSVector2i& window_size, const GSVector2i& video_size, const GSVector4i& video_active_rect, + const GSVector4i& source_rect, DisplayRotation rotation, DisplayAlignment alignment, + float pixel_aspect_ratio, bool integer_scale, DisplayFineCropMode fine_crop, + const std::span& fine_crop_amount, GSVector4i* out_source_rect, + GSVector4i* out_display_rect, GSVector4i* out_draw_rect, GSVector4* out_crop_amount = nullptr); +}; // namespace GPU - static bool DumpVRAMToFile(std::string path, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha, - Error* error = nullptr); - - void SoftReset(); - void ClearDisplay(); - - // Sets dots per scanline - void UpdateCRTCConfig(); - void UpdateCRTCDisplayParameters(); - - // Update ticks for this execution slice - void UpdateCRTCTickEvent(); - void UpdateCommandTickEvent(); - u8 UpdateOrGetGPUBusyPct(); - - // Updates dynamic bits in GPUSTAT (ready to send VRAM/ready to receive DMA) - void UpdateDMARequest(); - void UpdateGPUIdle(); - - /// Updates drawing area that's suitable for clamping. - void SetClampedDrawingArea(); - - /// Sets/decodes GP0(E1h) (set draw mode). - void SetDrawMode(u16 bits); - - /// Sets/decodes polygon/rectangle texture palette value. - void SetTexturePalette(u16 bits); - - /// Sets/decodes texture window bits. - void SetTextureWindow(u32 value); - - u32 ReadGPUREAD(); - void FinishVRAMWrite(); - - /// Returns the number of vertices in the buffered poly-line. - u32 GetPolyLineVertexCount() const; - - void AddCommandTicks(TickCount ticks); - - void WriteGP1(u32 value); - void EndCommand(); - void ExecuteCommands(); - void TryExecuteCommands(); - void HandleGetGPUInfoCommand(u32 value); - void UpdateCLUTIfNeeded(GPUTextureMode texmode, GPUTexturePaletteReg clut); - void InvalidateCLUT(); - bool IsCLUTValid() const; - - void ReadVRAM(u16 x, u16 y, u16 width, u16 height); - void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask); - - void PrepareForDraw(); - void FinishPolyline(); - void FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const; - - void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded, bool textured, - bool semitransparent); - void AddDrawRectangleTicks(const GSVector4i rect, bool textured, bool semitransparent); - void AddDrawLineTicks(const GSVector4i rect, bool shaded); - - GPUSTAT m_GPUSTAT = {}; - - bool m_console_is_pal = false; - bool m_set_texture_disable_mask = false; - bool m_drawing_area_changed = false; - bool m_force_progressive_scan = false; - - struct DrawMode - { - static constexpr u16 PALETTE_MASK = UINT16_C(0b0111111111111111); - static constexpr u32 TEXTURE_WINDOW_MASK = UINT32_C(0b11111111111111111111); - - // original values - GPUDrawModeReg mode_reg; - GPUTexturePaletteReg palette_reg; // from vertex - u32 texture_window_value; - - // decoded values - // TODO: Make this a command - GPUTextureWindow texture_window; - bool texture_x_flip; - bool texture_y_flip; - } m_draw_mode = {}; - - GPUDrawingArea m_drawing_area = {}; - GPUDrawingOffset m_drawing_offset = {}; - - GSVector4i m_clamped_drawing_area = {}; - - struct CRTCState - { - struct Regs - { - static constexpr u32 DISPLAY_ADDRESS_START_MASK = 0b111'11111111'11111110; - static constexpr u32 HORIZONTAL_DISPLAY_RANGE_MASK = 0b11111111'11111111'11111111; - static constexpr u32 VERTICAL_DISPLAY_RANGE_MASK = 0b1111'11111111'11111111; - - union - { - u32 display_address_start; - BitField X; - BitField Y; - }; - union - { - u32 horizontal_display_range; - BitField X1; - BitField X2; - }; - - union - { - u32 vertical_display_range; - BitField Y1; - BitField Y2; - }; - } regs; - - u16 dot_clock_divider; - - // Size of the simulated screen in pixels. Depending on crop mode, this may include overscan area. - u16 display_width; - u16 display_height; - - // Top-left corner in screen coordinates where the outputted portion of VRAM is first visible. - u16 display_origin_left; - u16 display_origin_top; - - // Rectangle in VRAM coordinates describing the area of VRAM that is visible on screen. - u16 display_vram_left; - u16 display_vram_top; - u16 display_vram_width; - u16 display_vram_height; - - // Visible range of the screen, in GPU ticks/lines. Clamped to lie within the active video region. - u16 horizontal_visible_start; - u16 horizontal_visible_end; - u16 vertical_visible_start; - u16 vertical_visible_end; - - u16 horizontal_display_start; - u16 horizontal_display_end; - u16 vertical_display_start; - u16 vertical_display_end; - - u16 horizontal_active_start; - u16 horizontal_active_end; - - u16 horizontal_total; - u16 vertical_total; - - u16 current_scanline; - TickCount fractional_ticks; - TickCount current_tick_in_scanline; - - TickCount fractional_dot_ticks; // only used when timer0 is enabled - - bool in_hblank; - bool in_vblank; - - /// 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...) - u8 interlaced_field; - u8 interlaced_display_field; - - /// 0 if the currently-displayed field is on an even line in VRAM, otherwise 1. - u8 active_line_lsb; - - ALWAYS_INLINE void UpdateHBlankFlag() - { - in_hblank = - (current_tick_in_scanline < horizontal_active_start || current_tick_in_scanline >= horizontal_active_end); - } - } m_crtc_state = {}; - - u32 m_command_total_words = 0; - TickCount m_pending_command_ticks = 0; - u32 m_active_ticks_since_last_update = 0; - - /// True if currently executing/syncing. - bool m_executing_commands = false; - BlitterState m_blitter_state = BlitterState::Idle; - - struct VRAMTransfer - { - u16 x; - u16 y; - u16 width; - u16 height; - u16 col; - u16 row; - } m_vram_transfer = {}; - - // One byte free, store the GPU usage here. - u8 m_last_gpu_busy_pct = 0; - - // These are the bits from the palette register, but zero extended to 32-bit, so we can have an "invalid" value. - // If an extra byte is ever not needed here for padding, the 8-bit flag could be packed into the MSB of this value. - bool m_current_clut_is_8bit = false; - u32 m_current_clut_reg_bits = {}; - - /// GPUREAD value for non-VRAM-reads. - u32 m_GPUREAD_latch = 0; - - std::unique_ptr m_gpu_dump; - - HeapFIFOQueue m_fifo; - TickCount m_max_run_ahead = 128; - u32 m_fifo_size = 128; - u32 m_blit_remaining_words; - GPURenderCommand m_render_command{}; - std::vector m_blit_buffer; - std::vector m_polyline_buffer; - - ALWAYS_INLINE u32 FifoPop() { return Truncate32(m_fifo.Pop()); } - ALWAYS_INLINE u32 FifoPeek() { return Truncate32(m_fifo.Peek()); } - ALWAYS_INLINE u32 FifoPeek(u32 i) { return Truncate32(m_fifo.Peek(i)); } - -private: - using GP0CommandHandler = bool (GPU::*)(); - using GP0CommandHandlerTable = std::array; - static GP0CommandHandlerTable GenerateGP0CommandHandlerTable(); - - // Rendering commands, returns false if not enough data is provided - bool HandleUnknownGP0Command(); - bool HandleNOPCommand(); - bool HandleClearCacheCommand(); - bool HandleInterruptRequestCommand(); - bool HandleSetDrawModeCommand(); - bool HandleSetTextureWindowCommand(); - bool HandleSetDrawingAreaTopLeftCommand(); - bool HandleSetDrawingAreaBottomRightCommand(); - bool HandleSetDrawingOffsetCommand(); - bool HandleSetMaskBitCommand(); - bool HandleRenderPolygonCommand(); - bool HandleRenderRectangleCommand(); - bool HandleRenderLineCommand(); - bool HandleRenderPolyLineCommand(); - bool HandleFillRectangleCommand(); - bool HandleCopyRectangleCPUToVRAMCommand(); - bool HandleCopyRectangleVRAMToCPUCommand(); - bool HandleCopyRectangleVRAMToVRAMCommand(); - - static const GP0CommandHandlerTable s_GP0_command_handler_table; -}; - -extern GPU g_gpu; extern u16 g_vram[VRAM_SIZE / sizeof(u16)]; extern u16 g_gpu_clut[GPU_CLUT_SIZE]; diff --git a/src/core/gpu_dump.cpp b/src/core/gpu_dump.cpp index 25a70acdd..2687d1335 100644 --- a/src/core/gpu_dump.cpp +++ b/src/core/gpu_dump.cpp @@ -76,7 +76,7 @@ std::unique_ptr GPUDump::Recorder::Create(std::string path, s ret = std::unique_ptr(new Recorder(std::move(fp), num_frames, std::move(path))); ret->WriteHeaders(serial); - g_gpu.WriteCurrentVideoModeToDump(ret.get()); + GPU::WriteCurrentVideoModeToDump(ret.get()); ret->WriteCurrentVRAM(); // Write start of stream. @@ -285,7 +285,7 @@ void GPUDump::Recorder::WriteHeaders(std::string_view serial) // Write textual video mode. BeginPacket(PacketType::TextualVideoFormat); - WriteString(g_gpu.IsInPALMode() ? "PAL" : "NTSC"); + WriteString(GPU::IsInPALMode() ? "PAL" : "NTSC"); EndPacket(); // Write DuckStation version. @@ -520,7 +520,7 @@ void GPUDump::Player::ProcessPacket(const PacketRef& pkt) if (pkt.type <= PacketType::VSyncEvent) { // gp0/gp1/vsync => direct to gpu - g_gpu.ProcessGPUDumpPacket(pkt.type, pkt.data); + GPU::ProcessGPUDumpPacket(pkt.type, pkt.data); return; } } diff --git a/src/core/gpu_sw_rasterizer.cpp b/src/core/gpu_sw_rasterizer.cpp index 3ae9378ca..c90c6e990 100644 --- a/src/core/gpu_sw_rasterizer.cpp +++ b/src/core/gpu_sw_rasterizer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "gpu_sw_rasterizer.h" @@ -7,6 +7,7 @@ #include "cpuinfo.h" +#include "common/assert.h" #include "common/gsvector.h" #include "common/log.h" #include "common/string_util.h" diff --git a/src/core/gpu_types.h b/src/core/gpu_types.h index 7d6937017..7a82a930e 100644 --- a/src/core/gpu_types.h +++ b/src/core/gpu_types.h @@ -167,7 +167,7 @@ union GP1SetDisplayMode BitField reverse_flag; }; -union GPUSTAT +union GPUSTATReg { // During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or } diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index 6e5595eff..9f3803903 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -208,13 +208,13 @@ void GunCon::UpdatePosition() { const auto& [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(m_cursor_index); - const GSVector2 display_pos = g_gpu.ConvertScreenCoordinatesToDisplayCoordinates(GSVector2(window_x, window_y)); + const GSVector2 display_pos = GPU::ConvertScreenCoordinatesToDisplayCoordinates(GSVector2(window_x, window_y)); // are we within the active display area? u32 tick, line; s32 offset_tick, offset_line; if ((display_pos < GSVector2::zero()).anytrue() || - !g_gpu.ConvertDisplayCoordinatesToBeamTicksAndLines(display_pos, m_x_scale, &tick, &line) || + !GPU::ConvertDisplayCoordinatesToBeamTicksAndLines(display_pos, m_x_scale, &tick, &line) || (offset_tick = static_cast(tick) + m_tick_offset) < 0 || (offset_line = static_cast(line) + m_line_offset) < 0 || m_shoot_offscreen) { @@ -225,7 +225,7 @@ void GunCon::UpdatePosition() } // 8MHz units for X = 44100*768*11/7 = 53222400 / 8000000 = 6.6528 - const double divider = static_cast(g_gpu.GetCRTCFrequency()) / 8000000.0; + const double divider = static_cast(GPU::GetCRTCFrequency()) / 8000000.0; m_position_x = static_cast(static_cast(offset_tick) / static_cast(divider)); m_position_y = static_cast(offset_line); DEV_LOG("Lightgun window coordinates {} -> tick {} line {} 8mhz ticks {}", display_pos, offset_tick, offset_line, diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 55cfb8d59..0675d11d5 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "imgui_overlays.h" @@ -29,6 +29,7 @@ #include "util/media_capture.h" #include "util/translation.h" +#include "common/assert.h" #include "common/align.h" #include "common/error.h" #include "common/file_system.h" @@ -118,7 +119,7 @@ static constexpr const std::array s_debug_wi {"Freecam", "Free Camera", ":icons/applications-system.png", >E::DrawFreecamWindow, 510, 500}, {"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 820, 950}, {"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 820, 555}, - {"GPU", "GPU State", ":icons/applications-system.png", [](float sc) { g_gpu.DrawDebugStateWindow(sc); }, 450, 550}, + {"GPU", "GPU State", ":icons/applications-system.png", &GPU::DrawDebugStateWindow, 450, 550}, {"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 860, 180}, {"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 300, 350}, {"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 95}, @@ -443,10 +444,10 @@ void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position { const u32 resolution_scale = gpu->GetResolutionScale(); const bool pgxp = gpu->IsUsingHardwareBackend() && g_gpu_settings.gpu_pgxp_enable; - const auto [display_width, display_height] = g_gpu.GetFullDisplayResolution(); // NOTE: Racey read. - const bool interlaced = g_gpu.IsInterlacedDisplayEnabled(); - const bool progressive_forced = g_gpu.IsProgressiveDisplayScanForced(); - const bool pal = g_gpu.IsInPALMode(); + const auto [display_width, display_height] = GPU::GetFullDisplayResolution(); // NOTE: Racey read. + const bool interlaced = GPU::IsInterlacedDisplayEnabled(); + const bool progressive_forced = GPU::IsProgressiveDisplayScanForced(); + const bool pal = GPU::IsInPALMode(); text.format("{}x{} " BOLD("{} {}") " | {}x " BOLD("IR") "{}", display_width * resolution_scale, display_height * resolution_scale, pal ? "PAL" : "NTSC", interlaced ? "Interlaced" : (progressive_forced ? "Forced-Progressive" : "Progressive"), diff --git a/src/core/justifier.cpp b/src/core/justifier.cpp index 2f1a9d911..b1a6d53f6 100644 --- a/src/core/justifier.cpp +++ b/src/core/justifier.cpp @@ -219,12 +219,12 @@ void Justifier::UpdatePosition() const auto [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(m_cursor_index); - const GSVector2 display_pos = g_gpu.ConvertScreenCoordinatesToDisplayCoordinates(GSVector2(window_x, window_y)); + const GSVector2 display_pos = GPU::ConvertScreenCoordinatesToDisplayCoordinates(GSVector2(window_x, window_y)); // are we within the active display area? u32 tick, line; if ((display_pos < GSVector2::zero()).anytrue() || - !g_gpu.ConvertDisplayCoordinatesToBeamTicksAndLines(display_pos, m_x_scale, &tick, &line) || m_shoot_offscreen) + !GPU::ConvertDisplayCoordinatesToBeamTicksAndLines(display_pos, m_x_scale, &tick, &line) || m_shoot_offscreen) { DEV_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y); m_position_valid = false; @@ -237,11 +237,11 @@ void Justifier::UpdatePosition() m_irq_tick = static_cast(static_cast(tick) + System::ScaleTicksToOverclock(static_cast(m_tick_offset))); m_irq_first_line = static_cast(std::clamp(static_cast(line) + m_first_line_offset, - static_cast(g_gpu.GetCRTCActiveStartLine()), - static_cast(g_gpu.GetCRTCActiveEndLine()))); + static_cast(GPU::GetCRTCActiveStartLine()), + static_cast(GPU::GetCRTCActiveEndLine()))); m_irq_last_line = static_cast(std::clamp(static_cast(line) + m_last_line_offset, - static_cast(g_gpu.GetCRTCActiveStartLine()), - static_cast(g_gpu.GetCRTCActiveEndLine()))); + static_cast(GPU::GetCRTCActiveStartLine()), + static_cast(GPU::GetCRTCActiveEndLine()))); DEV_LOG("Lightgun window coordinates {},{} -> dpy {} -> tick {} line {} [{}-{}]", window_x, window_y, display_pos, tick, line, m_irq_first_line, m_irq_last_line); @@ -258,7 +258,7 @@ void Justifier::UpdateIRQEvent() return; u32 current_tick, current_line; - g_gpu.GetBeamPosition(¤t_tick, ¤t_line); + GPU::GetBeamPosition(¤t_tick, ¤t_line); u32 target_line; if (current_line < m_irq_first_line || current_line >= m_irq_last_line) @@ -266,7 +266,7 @@ void Justifier::UpdateIRQEvent() else target_line = current_line + 1; - const TickCount ticks_until_pos = g_gpu.GetSystemTicksUntilTicksAndLine(m_irq_tick, target_line); + const TickCount ticks_until_pos = GPU::GetSystemTicksUntilTicksAndLine(m_irq_tick, target_line); DEBUG_LOG("Triggering IRQ in {} ticks @ tick {} line {}", ticks_until_pos, m_irq_tick, target_line); m_irq_event.Schedule(ticks_until_pos); } diff --git a/src/core/performance_counters.cpp b/src/core/performance_counters.cpp index a9f8741e1..fa9bebcef 100644 --- a/src/core/performance_counters.cpp +++ b/src/core/performance_counters.cpp @@ -10,6 +10,7 @@ #include "util/media_capture.h" +#include "common/assert.h" #include "common/log.h" #include "common/threading.h" #include "common/timer.h" diff --git a/src/core/system.cpp b/src/core/system.cpp index 54331ef7c..2aa96c72f 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -490,7 +490,7 @@ void System::UpdateOverclock() s_state.max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); SPU::CPUClockChanged(); CDROM::CPUClockChanged(); - g_gpu.CPUClockChanged(); + GPU::CPUClockChanged(); Timers::CPUClocksChanged(); UpdateThrottlePeriod(); } @@ -1009,7 +1009,7 @@ void System::RecreateGPU(GPURenderer renderer) ClearMemorySaveStates(true, false); - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); if (IsPaused()) VideoThread::PresentCurrentFrame(); } @@ -1029,7 +1029,7 @@ void System::LoadSettings(bool display_osd_messages) // Fix up automatic resolution scale, yuck. if (g_settings.gpu_automatic_resolution_scale && IsValid()) - g_settings.gpu_resolution_scale = g_gpu.CalculateAutomaticResolutionScale(); + g_settings.gpu_resolution_scale = GPU::CalculateAutomaticResolutionScale(); // show safe mode warning if it's toggled on, or on startup if (IsValidOrInitializing() && (display_osd_messages || (!previous_safe_mode && g_settings.disable_all_enhancements))) @@ -1807,8 +1807,7 @@ bool System::Initialize(std::unique_ptr disc, DiscRegion disc_region, b return false; } - // TODO: Drop class - g_gpu.Initialize(); + GPU::Initialize(); // Game info must be set prior to backend creation because of texture replacements. // We don't do it in UpdateRunningGame() when booting because it can fail in a number of locations. @@ -1889,7 +1888,7 @@ void System::DestroySystem() Timers::Shutdown(); Pad::Shutdown(); CDROM::Shutdown(); - g_gpu.Shutdown(); + GPU::Shutdown(); DMA::Shutdown(); PIO::Shutdown(); CPU::CodeCache::Shutdown(); @@ -2049,7 +2048,7 @@ void System::FrameDone() } // Late submission of frame. This is needed because the input poll can determine whether we need to rewind. - g_gpu.QueuePresentCurrentFrame(); + GPU::QueuePresentCurrentFrame(); SaveMemoryState(AllocateMemoryState()); } @@ -2350,7 +2349,7 @@ bool System::DoState(StateWrapper& sw, bool update_display) if (!sw.DoMarker("InterruptController") || !InterruptController::DoState(sw)) return false; - if (!sw.DoMarker("GPU") || !g_gpu.DoState(sw)) + if (!sw.DoMarker("GPU") || !GPU::DoState(sw)) return false; if (!sw.DoMarker("CDROM") || !CDROM::DoState(sw)) @@ -2415,7 +2414,7 @@ bool System::DoState(StateWrapper& sw, bool update_display) // If we're paused, need to update the display FB. if (update_display) - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); return true; } @@ -2627,7 +2626,7 @@ void System::DoMemoryState(StateWrapper& sw, MemorySaveState& mss, bool update_d SAVE_COMPONENT("DMA", DMA::DoState(sw)); SAVE_COMPONENT("InterruptController", InterruptController::DoState(sw)); - g_gpu.DoMemoryState(sw, mss); + GPU::DoMemoryState(sw, mss); SAVE_COMPONENT("CDROM", CDROM::DoState(sw)); SAVE_COMPONENT("Pad", Pad::DoState(sw, true)); @@ -2641,7 +2640,7 @@ void System::DoMemoryState(StateWrapper& sw, MemorySaveState& mss, bool update_d #undef SAVE_COMPONENT if (update_display) - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); } bool System::LoadBIOS(Error* error) @@ -2679,7 +2678,7 @@ void System::InternalReset() PIO::Reset(); DMA::Reset(); InterruptController::Reset(); - g_gpu.Reset(true); + GPU::Reset(true); CDROM::Reset(); Pad::Reset(); Timers::Reset(); @@ -2893,7 +2892,7 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo ResetThrottler(); if (update_display) - g_gpu.UpdateDisplay(true); + GPU::UpdateDisplay(true); return true; } @@ -3952,7 +3951,7 @@ bool System::DumpVRAM(std::string path, Error* error) return false; } - return g_gpu.DumpVRAMToFile(path, error); + return GPU::DumpVRAMToFile(path, error); } bool System::DumpSPURAM(std::string path, Error* error) @@ -4467,7 +4466,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.display_line_start_offset != old_settings.display_line_start_offset || g_settings.display_line_end_offset != old_settings.display_line_end_offset) { - g_gpu.UpdateSettings(old_settings); + GPU::UpdateSettings(old_settings); } if (g_settings.gpu_renderer != old_settings.gpu_renderer) @@ -4542,7 +4541,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) if (IsPaused()) { // resolution change needs display updated - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); VideoThread::PresentCurrentFrame(); } } @@ -4593,7 +4592,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) if (IsPaused()) { // and display the current frame on the new device - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); VideoThread::PresentCurrentFrame(); } } @@ -5458,7 +5457,7 @@ bool System::StartRecordingGPUDump(const char* path /*= nullptr*/, u32 num_frame .c_str(); } - return g_gpu.StartRecordingGPUDump(path, num_frames); + return GPU::StartRecordingGPUDump(path, num_frames); } void System::StopRecordingGPUDump() @@ -5466,7 +5465,7 @@ void System::StopRecordingGPUDump() if (!IsValid()) return; - g_gpu.StopRecordingGPUDump(); + GPU::StopRecordingGPUDump(); } static std::string_view GetCaptureTypeForMessage(bool capture_video, bool capture_audio) @@ -6029,8 +6028,8 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/) { const WindowInfo& wi = VideoThread::GetRenderWindowInfo(); requested_size = GPU::CalculateRenderWindowSize( - g_settings.display_fine_crop_mode, g_settings.display_fine_crop_amount, g_gpu.ComputePixelAspectRatio(), - GSVector2(g_gpu.GetCRTCVideoSize()), GSVector2(g_gpu.GetCRTCVRAMSourceRect().rsize()) * scale, + g_settings.display_fine_crop_mode, g_settings.display_fine_crop_amount, GPU::ComputePixelAspectRatio(), + GSVector2(GPU::GetCRTCVideoSize()), GSVector2(GPU::GetCRTCVRAMSourceRect().rsize()) * scale, GSVector2(GSVector2i(wi.surface_width, wi.surface_height))); } @@ -6078,7 +6077,7 @@ void System::UpdateGTEAspectRatio() { // Pre-apply the native aspect ratio correction to the window size. // MatchWindow does not correct the display aspect ratio, so we need to apply it here. - const float correction = g_gpu.ComputeAspectRatioCorrection(); + const float correction = GPU::ComputeAspectRatioCorrection(); custom_num = static_cast(std::max(std::round(static_cast(main_window_info.surface_width) / correction), 1.0f)); custom_denom = std::max(main_window_info.surface_height, 1u); @@ -6099,7 +6098,7 @@ void System::UpdateAutomaticResolutionScale() if (!IsValidOrInitializing() || !g_settings.gpu_automatic_resolution_scale) return; - const u32 new_scale = g_gpu.CalculateAutomaticResolutionScale(); + const u32 new_scale = GPU::CalculateAutomaticResolutionScale(); if (g_settings.gpu_resolution_scale == new_scale) return; @@ -6111,7 +6110,7 @@ void System::UpdateAutomaticResolutionScale() if (IsPaused()) { // resolution change needs display updated - g_gpu.UpdateDisplay(false); + GPU::UpdateDisplay(false); VideoThread::PresentCurrentFrame(); } } diff --git a/src/core/timers.cpp b/src/core/timers.cpp index a0f33166a..1b580f6e8 100644 --- a/src/core/timers.cpp +++ b/src/core/timers.cpp @@ -315,8 +315,8 @@ u32 Timers::ReadRegister(u32 offset) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || g_gpu.IsCRTCScanlinePending()) - g_gpu.SynchronizeCRTC(); + if (timer_index == 0 || GPU::IsCRTCScanlinePending()) + GPU::SynchronizeCRTC(); } s_state.sysclk_event.InvokeEarly(); @@ -329,8 +329,8 @@ u32 Timers::ReadRegister(u32 offset) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || g_gpu.IsCRTCScanlinePending()) - g_gpu.SynchronizeCRTC(); + if (timer_index == 0 || GPU::IsCRTCScanlinePending()) + GPU::SynchronizeCRTC(); } s_state.sysclk_event.InvokeEarly(); @@ -365,8 +365,8 @@ void Timers::WriteRegister(u32 offset, u32 value) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || g_gpu.IsCRTCScanlinePending()) - g_gpu.SynchronizeCRTC(); + if (timer_index == 0 || GPU::IsCRTCScanlinePending()) + GPU::SynchronizeCRTC(); } s_state.sysclk_event.InvokeEarly(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index bb393715d..a30d85951 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -3103,7 +3103,7 @@ void CoreThread::updatePerformanceCounters(const GPUBackend* gpu_backend) if (gpu_backend) { const u32 render_scale = gpu_backend->GetResolutionScale(); - std::tie(render_width, render_height) = g_gpu.GetFullDisplayResolution(); + std::tie(render_width, render_height) = GPU::GetFullDisplayResolution(); render_width *= render_scale; render_height *= render_scale; }