#include "interface.hpp" #include "../retroachievements/retroachievements.h" #include "rdp_device.hpp" #include "spirv.hpp" #include "spirv_crt.hpp" #include "wsi.hpp" #include "wsi_platform.hpp" #include #include #include #include using namespace Vulkan; #define DP_STATUS_XBUS_DMA 0x01 #define DP_STATUS_FREEZE 0x02 #define DP_STATUS_FLUSH 0x04 #define DP_STATUS_START_GCLK 0x008 #define DP_STATUS_TMEM_BUSY 0x010 #define DP_STATUS_PIPE_BUSY 0x020 #define DP_STATUS_CMD_BUSY 0x040 #define DP_STATUS_CBUF_READY 0x080 #define DP_STATUS_DMA_BUSY 0x100 #define DP_STATUS_END_VALID 0x200 #define DP_STATUS_START_VALID 0x400 enum dpc_registers { DPC_START_REG, DPC_END_REG, DPC_CURRENT_REG, DPC_STATUS_REG, DPC_CLOCK_REG, DPC_BUFBUSY_REG, DPC_PIPEBUSY_REG, DPC_TMEM_REG, DPC_REGS_COUNT }; enum vi_registers { VI_STATUS_REG, VI_ORIGIN_REG, VI_WIDTH_REG, VI_V_INTR_REG, VI_CURRENT_REG, VI_BURST_REG, VI_V_SYNC_REG, VI_H_SYNC_REG, VI_LEAP_REG, VI_H_START_REG, VI_V_START_REG, VI_V_BURST_REG, VI_X_SCALE_REG, VI_Y_SCALE_REG, VI_REGS_COUNT }; enum user_event_codes { USER_EVENT_SAVE_STATE = 1, USER_EVENT_LOAD_STATE = 2, USER_EVENT_EXIT_GAME = 3, USER_EVENT_FAST_FORWARD = 4, USER_EVENT_LOAD_REWIND = 5, }; typedef struct { uint32_t depthbuffer_address; uint32_t framebuffer_address; uint32_t framebuffer_y_offset; uint32_t texture_address; uint32_t framebuffer_pixel_size; uint32_t framebuffer_width; uint32_t texture_pixel_size; uint32_t texture_width; uint32_t framebuffer_height; bool depth_buffer_enabled; } FrameBufferInfo; typedef struct { uint32_t cmd_data[0x00040000 >> 2]; int cmd_cur; int cmd_ptr; uint32_t region; FrameBufferInfo frame_buffer_info; } RDP_DEVICE; static void *g_tmem = nullptr; static void *g_hidden_rdram = nullptr; typedef struct { uint32_t fps; uint32_t vis; } FPS_DATA; typedef struct { std::string message; uint64_t milliseconds; Vulkan::ImageHandle image; } Message; static SDL_Window *window; static RDP::CommandProcessor *processor; static SDL_WSIPlatform *wsi_platform; static WSI *wsi; static RDP_DEVICE rdp_device; static bool crop_letterbox; static CALL_BACK callback; static GFX_INFO gfx_info; static const uint32_t *fragment_spirv; static size_t fragment_size; static std::vector rdram_dirty; static uint64_t sync_signal; static TTF_Font *message_font; static std::queue messages; static TTF_Font *achievement_challenge_indicator_font; static std::vector achievement_challenge_indicators; static Vulkan::ImageHandle achievement_challenge_indicator_image; static Vulkan::ImageHandle achievement_progress_indicator_image; static std::map leaderboard_trackers; static bool display_challenge_indicator; static bool display_fps; static Vulkan::ImageHandle fps_image; static std::queue joystick_events; typedef struct { float SourceSize[4]; float OutputSize[4]; } Push; static const unsigned cmd_len_lut[64] = { 1, 1, 1, 1, 1, 1, 1, 1, 4, 6, 12, 14, 12, 14, 20, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; static void add_joystick_event(void *userdata) { JoystickEvent *joystick_event = (JoystickEvent *)userdata; joystick_events.push(*joystick_event); delete joystick_event; } bool sdl_event_filter(void *userdata, SDL_Event *event) { if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { callback.paused = false; callback.emu_running = false; } else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running) { wsi_platform->do_resize(); } else if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat) { SDL_Event user_event; switch (event->key.scancode) { case SDL_SCANCODE_RETURN: if (event->key.mod & SDL_KMOD_ALT) { gfx_info.fullscreen = !gfx_info.fullscreen; SDL_SetWindowFullscreen(window, gfx_info.fullscreen); } break; case SDL_SCANCODE_F: if (event->key.mod & SDL_KMOD_ALT) { SDL_zero(user_event); user_event.type = SDL_EVENT_USER; user_event.user.code = USER_EVENT_FAST_FORWARD; SDL_PushEvent(&user_event); } break; case SDL_SCANCODE_P: if (event->key.mod & SDL_KMOD_ALT) { callback.paused = !callback.paused; } break; case SDL_SCANCODE_AC_BACK: case SDL_SCANCODE_ESCAPE: if (gfx_info.fullscreen) { SDL_zero(user_event); user_event.type = SDL_EVENT_USER; user_event.user.code = USER_EVENT_EXIT_GAME; SDL_PushEvent(&user_event); } break; case SDL_SCANCODE_F1: display_fps = !display_fps; break; case SDL_SCANCODE_F4: crop_letterbox = !crop_letterbox; break; case SDL_SCANCODE_F5: SDL_zero(user_event); user_event.type = SDL_EVENT_USER; user_event.user.code = USER_EVENT_SAVE_STATE; SDL_PushEvent(&user_event); break; case SDL_SCANCODE_F6: SDL_zero(user_event); user_event.type = SDL_EVENT_USER; user_event.user.code = USER_EVENT_LOAD_REWIND; SDL_PushEvent(&user_event); break; case SDL_SCANCODE_F7: SDL_zero(user_event); user_event.type = SDL_EVENT_USER; user_event.user.code = USER_EVENT_LOAD_STATE; SDL_PushEvent(&user_event); break; case SDL_SCANCODE_F8: if (messages.empty()) SDL_RunOnMainThread(ra_display_inprogress_achievements, nullptr, false); break; case SDL_SCANCODE_F9: display_challenge_indicator = !display_challenge_indicator; rdp_onscreen_message( std::format("Challenge indicators: {}", display_challenge_indicator ? "ON" : "OFF") .c_str(), MESSAGE_VERY_SHORT); break; case SDL_SCANCODE_F12: callback.reset_game = true; break; case SDL_SCANCODE_LEFTBRACKET: if (event->key.mod & SDL_KMOD_ALT) { callback.decrease_input_delay = true; } else { callback.lower_volume = true; } break; case SDL_SCANCODE_RIGHTBRACKET: if (event->key.mod & SDL_KMOD_ALT) { callback.increase_input_delay = true; } else { callback.raise_volume = true; } break; case SDL_SCANCODE_SLASH: callback.frame_advance = true; break; case SDL_SCANCODE_0: case SDL_SCANCODE_1: case SDL_SCANCODE_2: case SDL_SCANCODE_3: case SDL_SCANCODE_4: case SDL_SCANCODE_5: case SDL_SCANCODE_6: case SDL_SCANCODE_7: case SDL_SCANCODE_8: case SDL_SCANCODE_9: if (event->key.mod & SDL_KMOD_ALT) { if (event->key.scancode == SDL_SCANCODE_0) callback.save_state_slot = 0; else callback.save_state_slot = event->key.scancode - SDL_SCANCODE_1 + 1; } break; default: break; } } else if (event->type == SDL_EVENT_USER) { switch (event->user.code) { case USER_EVENT_SAVE_STATE: callback.save_state = true; break; case USER_EVENT_LOAD_REWIND: callback.load_rewind = true; break; case USER_EVENT_LOAD_STATE: callback.load_state = true; break; case USER_EVENT_EXIT_GAME: callback.emu_running = false; break; case USER_EVENT_FAST_FORWARD: callback.enable_speedlimiter = !callback.enable_speedlimiter; break; default: break; } } else if (event->type == SDL_EVENT_JOYSTICK_ADDED) { JoystickEvent *joystick_event = new JoystickEvent; joystick_event->joystick_id = event->jdevice.which; joystick_event->connected = true; SDL_RunOnMainThread(add_joystick_event, joystick_event, false); } else if (event->type == SDL_EVENT_JOYSTICK_REMOVED) { JoystickEvent *joystick_event = new JoystickEvent; joystick_event->joystick_id = event->jdevice.which; joystick_event->connected = false; SDL_RunOnMainThread(add_joystick_event, joystick_event, false); } else if (event->type == SDL_EVENT_WILL_ENTER_BACKGROUND) { wsi->end_frame(); wsi->deinit_surface_and_swapchain(); } else if (event->type == SDL_EVENT_RENDER_DEVICE_RESET) { wsi->init_surface_swapchain(); wsi->begin_frame(); } return 0; } JoystickEvent get_joystick_event() { if (joystick_events.empty()) return JoystickEvent{0, false}; JoystickEvent joystick_event = joystick_events.front(); joystick_events.pop(); return joystick_event; } void rdp_idle() { processor->idle(); sync_signal = 0; rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false); } static void rdp_new_processor() { RDP::CommandProcessorFlags flags = RDP::COMMAND_PROCESSOR_FLAG_HOST_VISIBLE_HIDDEN_RDRAM_BIT | RDP::COMMAND_PROCESSOR_FLAG_HOST_VISIBLE_TMEM_BIT; if (gfx_info.upscale == 2) { flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT; flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT; } else if (gfx_info.upscale == 4) { flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT; flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_4X_BIT; } else if (gfx_info.upscale == 8) { flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT; flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_8X_BIT; } else { gfx_info.upscale = 1; } processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags); g_tmem = processor->get_tmem(); if (!g_tmem) { LOGE("Failed to get tmem\n"); } g_hidden_rdram = processor->begin_read_hidden_rdram(); if (!g_hidden_rdram) { LOGE("Failed to get hidden_rdram\n"); } sync_signal = 0; rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false); } static ImageHandle create_message_image(Vulkan::Device &device, int width, TTF_Font *font, const char *message) { if (strstr(message, "\n")) width = 0; SDL_Color fg = {255, 255, 255, 255}; SDL_Color bg = {0, 0, 0, 0}; SDL_Surface *surface = TTF_RenderText_LCD_Wrapped(font, message, 0, fg, bg, width); ImageCreateInfo info = ImageCreateInfo::immutable_2d_image( surface->w, surface->h, VK_FORMAT_B8G8R8A8_UNORM, false); ImageInitialData initial_data = {}; initial_data.data = surface->pixels; initial_data.row_length = surface->pitch / 4; initial_data.image_height = surface->h; ImageHandle handle = device.create_image(info, &initial_data); SDL_DestroySurface(surface); return handle; } void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size, uint32_t save_state_slot) { memset(&rdp_device, 0, sizeof(RDP_DEVICE)); memset(&callback, 0, sizeof(CALL_BACK)); window = (SDL_Window *)_window; SDL_SyncWindow(window); bool result = SDL_AddEventWatch(sdl_event_filter, nullptr); if (!result) { LOGE("Could not add event watch.\n"); return; } gfx_info = _gfx_info; if (gfx_info.crt) { fragment_spirv = crt_fragment_spirv; fragment_size = sizeof(crt_fragment_spirv); } else { fragment_spirv = plain_fragment_spirv; fragment_size = sizeof(plain_fragment_spirv); } wsi = new WSI; wsi_platform = new SDL_WSIPlatform; wsi_platform->set_window(window); wsi->set_platform(wsi_platform); if (gfx_info.vsync) { // VK_PRESENT_MODE_MAILBOX_KHR, fallback to VK_PRESENT_MODE_FIFO_KHR wsi->set_present_mode(PresentMode::UnlockedNoTearing); } else { // VK_PRESENT_MODE_MAILBOX_KHR, fallback to VK_PRESENT_MODE_IMMEDIATE_KHR wsi->set_present_mode(PresentMode::UnlockedMaybeTear); } wsi->set_backbuffer_srgb(false); Context::SystemHandles handles = {}; if (!::Vulkan::Context::init_loader( (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr())) { rdp_close(); return; } if (!wsi->init_simple(1, handles)) { rdp_close(); return; } rdp_new_processor(); if (!processor->device_is_supported()) { rdp_close(); return; } message_font = TTF_OpenFontIO(SDL_IOFromConstMem(font, font_size), true, 25.0); achievement_challenge_indicator_font = TTF_OpenFontIO(SDL_IOFromConstMem(font, font_size), true, 12.0); if (!message_font || !achievement_challenge_indicator_font) { rdp_close(); return; } wsi->begin_frame(); callback.emu_running = true; callback.enable_speedlimiter = true; callback.paused = false; callback.save_state_slot = save_state_slot; crop_letterbox = false; messages = std::queue(); display_challenge_indicator = true; achievement_challenge_indicators.clear(); leaderboard_trackers.clear(); achievement_challenge_indicator_image = Vulkan::ImageHandle(); achievement_progress_indicator_image = Vulkan::ImageHandle(); fps_image = Vulkan::ImageHandle(); display_fps = false; } void rdp_close() { display_fps = false; g_tmem = nullptr; g_hidden_rdram = nullptr; messages = std::queue(); achievement_challenge_indicator_image = Vulkan::ImageHandle(); achievement_progress_indicator_image = Vulkan::ImageHandle(); fps_image = Vulkan::ImageHandle(); if (wsi) wsi->end_frame(); if (message_font) { TTF_CloseFont(message_font); message_font = nullptr; } if (achievement_challenge_indicator_font) { TTF_CloseFont(achievement_challenge_indicator_font); achievement_challenge_indicator_font = nullptr; } if (processor) { delete processor; processor = nullptr; } if (wsi) { delete wsi; wsi = nullptr; } if (wsi_platform) { delete wsi_platform; wsi_platform = nullptr; } SDL_RemoveEventWatch(sdl_event_filter, nullptr); } static void calculate_viewport(float *x, float *y, float *width, float *height, uint32_t display_height) { uint32_t display_width = gfx_info.widescreen ? display_height * 16 / 9 : display_height * 4 / 3; int w, h; SDL_GetWindowSize(window, &w, &h); if (gfx_info.integer_scaling) { // Integer scaling path int scale_x = w / display_width; int scale_y = h / display_height; int scale = (scale_x < scale_y) ? scale_x : scale_y; if (scale < 1) scale = 1; // Calculate scaled dimensions int scaled_width = display_width * scale; int scaled_height = display_height * scale; *width = scaled_width; *height = scaled_height; // Center the viewport int integer_x = (w - *width) / 2.0f; int integer_y = (h - *height) / 2.0f; *x = integer_x; *y = integer_y; } else { // Regular scaling path - maintain aspect ratio float scale_x = w / (float)display_width; float scale_y = h / (float)display_height; float scale = (scale_x < scale_y) ? scale_x : scale_y; *width = display_width * scale; *height = display_height * scale; // Center the viewport *x = (w - *width) / 2.0f; *y = (h - *height) / 2.0f; } } static void draw_indicator(CommandBufferHandle cmd, Vulkan::ImageHandle indicator_image, VkViewport vp) { cmd->set_texture(0, 0, indicator_image->get_view(), Vulkan::StockSampler::NearestClamp); vp.x = vp.x + vp.width - indicator_image->get_width(); vp.y = vp.y + vp.height - indicator_image->get_height(); vp.height = indicator_image->get_height(); vp.width = indicator_image->get_width(); cmd->set_viewport(vp); cmd->draw(3); } static void draw_fps(CommandBufferHandle cmd, VkViewport vp) { cmd->set_texture(0, 0, fps_image->get_view(), Vulkan::StockSampler::NearestClamp); vp.y = vp.y + vp.height - fps_image->get_height(); vp.height = fps_image->get_height(); vp.width = fps_image->get_width(); cmd->set_viewport(vp); cmd->draw(3); } static void pop_message(void *userdata) { messages.pop(); } static uint32_t pop_message_callback(void *userdata, SDL_TimerID timerID, uint32_t interval) { SDL_RunOnMainThread(pop_message, NULL, false); return 0; } static void render_frame(Vulkan::Device &device) { RDP::ScanoutOptions options = {}; options.persist_frame_on_invalid_input = true; options.blend_previous_frame = !gfx_info.ssaa; options.upscale_deinterlacing = gfx_info.ssaa; if (gfx_info.ssaa) { switch (gfx_info.upscale) { case 2: options.downscale_steps = 1; break; case 4: options.downscale_steps = 2; break; case 8: options.downscale_steps = 3; break; default: options.downscale_steps = 0; break; } } if (crop_letterbox && gfx_info.widescreen) { options.crop_rect.enable = true; if (gfx_info.PAL) { options.crop_rect.top = 36; options.crop_rect.bottom = 36; } else { options.crop_rect.top = 30; options.crop_rect.bottom = 30; } } Vulkan::ImageHandle image = processor->scanout(options); Vulkan::ResourceLayout vertex_layout = {}; Vulkan::ResourceLayout fragment_layout = {}; fragment_layout.output_mask = 1 << 0; fragment_layout.sets[0].sampled_image_mask = 1 << 0; if (gfx_info.crt) fragment_layout.push_constant_size = sizeof(Push); // This request is cached. auto *program = device.request_program(vertex_spirv, sizeof(vertex_spirv), fragment_spirv, fragment_size, &vertex_layout, &fragment_layout); // Blit image on screen. auto cmd = device.request_command_buffer(); { auto rp = device.get_swapchain_render_pass( Vulkan::SwapchainRenderPass::ColorOnly); cmd->begin_render_pass(rp); cmd->set_program(program); // Basic default render state. cmd->set_opaque_state(); cmd->set_depth_test(false, false); cmd->set_cull_mode(VK_CULL_MODE_NONE); VkViewport vp = cmd->get_viewport(); // If we don't have an image, we just get a cleared screen in the render // pass. if (image) { calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height, image->get_height() / gfx_info.upscale); if (gfx_info.crt) { // Set shader parameters Push push = { {float(image->get_width()), float(image->get_height()), 1.0f / float(image->get_width()), 1.0f / float(image->get_height())}, {vp.width, vp.height, 1.0f / vp.width, 1.0f / vp.height}, }; cmd->push_constants(&push, 0, sizeof(push)); } cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::NearestClamp); cmd->set_viewport(vp); // The vertices are constants in the shader. // Draws fullscreen quad using oversized triangle. cmd->draw(3); } if (!messages.empty()) { Message *message = &messages.front(); if (!message->image) { message->image = create_message_image(device, vp.width, message_font, message->message.c_str()); SDL_AddTimer(message->milliseconds, pop_message_callback, NULL); } cmd->set_texture(0, 0, message->image->get_view(), Vulkan::StockSampler::NearestClamp); vp.x = floor(vp.x + (vp.width - message->image->get_width()) / 2); vp.y = vp.y + vp.height - message->image->get_height(); vp.height = message->image->get_height(); vp.width = message->image->get_width(); cmd->set_viewport(vp); cmd->draw(3); } else if (achievement_progress_indicator_image) { draw_indicator(cmd, achievement_progress_indicator_image, vp); } else if (achievement_challenge_indicator_image && display_challenge_indicator) { draw_indicator(cmd, achievement_challenge_indicator_image, vp); } if (messages.empty() && display_fps && fps_image) { draw_fps(cmd, vp); } cmd->end_render_pass(); } device.submit(cmd); } void rdp_set_vi_register(uint32_t reg, uint32_t value) { processor->set_vi_register(RDP::VIRegister(reg), value); } void rdp_render_frame() { auto &device = wsi->get_device(); render_frame(device); } void rdp_update_screen() { if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { return; } if (!wsi->end_frame()) { LOGE("End frame failed\n"); SDL_PumpEvents(); // For Android to trigger pause event } wsi->begin_frame(); } CALL_BACK rdp_check_callback() { CALL_BACK return_value = callback; callback.save_state = false; callback.load_rewind = false; callback.load_state = false; callback.reset_game = false; callback.lower_volume = false; callback.raise_volume = false; callback.decrease_input_delay = false; callback.increase_input_delay = false; callback.frame_advance = false; return return_value; } void rdp_check_framebuffers(uint32_t address, uint32_t length) { if (sync_signal) { address >>= 3; length = (length + 7) >> 3; if (address >= rdram_dirty.size()) return; uint32_t end_addr = std::min(address + length, static_cast(rdram_dirty.size())); auto it = std::find(rdram_dirty.begin() + address, rdram_dirty.begin() + end_addr, true); if (it != rdram_dirty.begin() + end_addr) { processor->wait_for_timeline(sync_signal); rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false); sync_signal = 0; } } } size_t rdp_state_size() { return sizeof(RDP_DEVICE) + 0x1000 + processor->get_hidden_rdram_size(); } void rdp_save_state(uint8_t *state) { memcpy(state, &rdp_device, sizeof(RDP_DEVICE)); if (g_tmem) { memcpy(state + sizeof(RDP_DEVICE), g_tmem, 0x1000); } if (g_hidden_rdram) { memcpy(state + sizeof(RDP_DEVICE) + 0x1000, g_hidden_rdram, processor->get_hidden_rdram_size()); } } void rdp_load_state(GFX_INFO _gfx_info, const uint8_t *state) { gfx_info = _gfx_info; memcpy(&rdp_device, state, sizeof(RDP_DEVICE)); if (g_tmem) { memcpy(g_tmem, state + sizeof(RDP_DEVICE), 0x1000); } if (g_hidden_rdram) { memcpy(g_hidden_rdram, state + sizeof(RDP_DEVICE) + 0x1000, processor->get_hidden_rdram_size()); } } static void push_onscreen_message(void *data) { Message *message = (Message *)data; messages.push(*message); delete message; } void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds) { Message *data = new Message; data->message = message; data->milliseconds = static_cast(milliseconds); data->image = Vulkan::ImageHandle(); SDL_RunOnMainThread(push_onscreen_message, data, false); } uint32_t pixel_size(uint32_t pixel_type, uint32_t area) { switch (pixel_type) { case 0: return area / 2; case 1: return area; case 2: return area * 2; case 3: return area * 4; default: LOGE("Invalid pixel size: %u\n", pixel_type); return 0; } } uint64_t rdp_process_commands() { uint64_t interrupt_timer = 0; const uint32_t DP_CURRENT = *gfx_info.DPC_CURRENT_REG & 0x00FFFFF8; const uint32_t DP_END = *gfx_info.DPC_END_REG & 0x00FFFFF8; int length = DP_END - DP_CURRENT; if (length <= 0) return interrupt_timer; length = unsigned(length) >> 3; if ((rdp_device.cmd_ptr + length) & ~(0x0003FFFF >> 3)) return interrupt_timer; uint32_t offset = DP_CURRENT; if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA) { do { offset &= 0xFF8; rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = SDL_Swap32BE( *reinterpret_cast(gfx_info.DMEM + offset)); rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = SDL_Swap32BE( *reinterpret_cast(gfx_info.DMEM + offset + 4)); offset += sizeof(uint64_t); rdp_device.cmd_ptr++; } while (--length > 0); } else { if (DP_END > 0x7ffffff || DP_CURRENT > 0x7ffffff) { return interrupt_timer; } else { do { offset &= 0xFFFFF8; rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = *reinterpret_cast(gfx_info.RDRAM + offset); rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = *reinterpret_cast(gfx_info.RDRAM + offset + 4); offset += sizeof(uint64_t); rdp_device.cmd_ptr++; } while (--length > 0); } } while (rdp_device.cmd_cur - rdp_device.cmd_ptr < 0) { uint32_t w1 = rdp_device.cmd_data[2 * rdp_device.cmd_cur]; uint32_t w2 = rdp_device.cmd_data[2 * rdp_device.cmd_cur + 1]; uint32_t command = (w1 >> 24) & 63; int cmd_length = cmd_len_lut[command]; if (rdp_device.cmd_ptr - rdp_device.cmd_cur - cmd_length < 0) { *gfx_info.DPC_START_REG = *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG; return interrupt_timer; } if (command >= 8) processor->enqueue_command(cmd_length * 2, &rdp_device.cmd_data[2 * rdp_device.cmd_cur]); switch (RDP::Op(command)) { case RDP::Op::FillTriangle: case RDP::Op::FillZBufferTriangle: case RDP::Op::TextureTriangle: case RDP::Op::TextureZBufferTriangle: case RDP::Op::ShadeTriangle: case RDP::Op::ShadeZBufferTriangle: case RDP::Op::ShadeTextureTriangle: case RDP::Op::ShadeTextureZBufferTriangle: case RDP::Op::TextureRectangle: case RDP::Op::TextureRectangleFlip: case RDP::Op::FillRectangle: { uint32_t offset_address = (rdp_device.frame_buffer_info.framebuffer_address + pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3; if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address]) { uint32_t end_addr = std::min( offset_address + ((pixel_size( rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast(rdram_dirty.size())); std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true); } if (rdp_device.frame_buffer_info.depth_buffer_enabled) { offset_address = (rdp_device.frame_buffer_info.depthbuffer_address + pixel_size(2, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3; if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address]) { uint32_t end_addr = std::min( offset_address + ((pixel_size( 2, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast(rdram_dirty.size())); std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true); } } } break; case RDP::Op::LoadTLut: case RDP::Op::LoadTile: { uint32_t upper_left_t = (w1 & 0xFFF) >> 2; uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3; if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address]) { uint32_t lower_right_t = (w2 & 0xFFF) >> 2; uint32_t end_addr = std::min( offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, (lower_right_t - upper_left_t) * rdp_device.frame_buffer_info.texture_width) + 7) >> 3), static_cast(rdram_dirty.size())); std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true); } } break; case RDP::Op::LoadBlock: { uint32_t upper_left_s = ((w1 >> 12) & 0xFFF); uint32_t upper_left_t = (w1 & 0xFFF); uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_s + upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3; if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address]) { uint32_t lower_right_s = ((w2 >> 12) & 0xFFF); uint32_t end_addr = std::min( offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, lower_right_s - upper_left_s) + 7) >> 3), static_cast(rdram_dirty.size())); std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true); } } break; case RDP::Op::SetColorImage: rdp_device.frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF); rdp_device.frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3; rdp_device.frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1; break; case RDP::Op::SetMaskImage: rdp_device.frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF); break; case RDP::Op::SetTextureImage: rdp_device.frame_buffer_info.texture_address = (w2 & 0x00FFFFFF); rdp_device.frame_buffer_info.texture_pixel_size = (w1 >> 19) & 0x3; rdp_device.frame_buffer_info.texture_width = (w1 & 0x3FF) + 1; break; case RDP::Op::SetScissor: { uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2; uint32_t upper_left_y = (w1 & 0xFFF) >> 2; uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2; uint32_t lower_right_y = (w2 & 0xFFF) >> 2; if (lower_right_x > upper_left_x && lower_right_y > upper_left_y) { rdp_device.region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y); } else { rdp_device.region = 0; } rdp_device.frame_buffer_info.framebuffer_y_offset = upper_left_y; rdp_device.frame_buffer_info.framebuffer_height = lower_right_y - upper_left_y; } break; case RDP::Op::SetOtherModes: { uint8_t cycle_type = (w1 >> 20) & 3; uint8_t depth_read_write = (w2 >> 4) & 3; rdp_device.frame_buffer_info.depth_buffer_enabled = ((cycle_type & 2) == 0) && (depth_read_write != 0); } break; case RDP::Op::SyncFull: sync_signal = processor->signal_timeline(); interrupt_timer = rdp_device.region; if (interrupt_timer == 0) interrupt_timer = 5000; break; default: break; } rdp_device.cmd_cur += cmd_length; } rdp_device.cmd_ptr = 0; rdp_device.cmd_cur = 0; *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG; return interrupt_timer; } static void rdp_set_fps_callback(void *userdata) { FPS_DATA *data = (FPS_DATA *)userdata; if (!wsi) { delete data; return; } fps_image = create_message_image( wsi->get_device(), 0, achievement_challenge_indicator_font, std::format("FPS: {} VI/S: {}", data->fps, data->vis).c_str()); delete data; } void rdp_set_fps(uint32_t fps, uint32_t vis) { if (display_fps) { FPS_DATA *data = new FPS_DATA; data->fps = fps; data->vis = vis; SDL_RunOnMainThread(rdp_set_fps_callback, data, false); } } static void update_challenge_indicator() { std::string message; for (const auto &leaderboard_tracker : leaderboard_trackers) { message += leaderboard_tracker.second; message += '\n'; } if (!leaderboard_trackers.empty() && !achievement_challenge_indicators.empty()) { message += "---\n"; } const auto &v = achievement_challenge_indicators; for (size_t i = 0; i < std::min(v.size(), 5); ++i) { message += v[i]; message += '\n'; } if (message.empty()) { achievement_challenge_indicator_image = Vulkan::ImageHandle(); } else { achievement_challenge_indicator_image = create_message_image( wsi->get_device(), 0, achievement_challenge_indicator_font, message.c_str()); } } void achievement_challenge_indicator_add(const char *achievement_title) { achievement_challenge_indicators.push_back(achievement_title); update_challenge_indicator(); } void achievement_challenge_indicator_remove(const char *achievement_title) { std::erase(achievement_challenge_indicators, achievement_title); update_challenge_indicator(); } void achievement_progress_add(const char *achievement_title, const char *progress) { std::string message = std::format("{}: {}", achievement_title, progress); achievement_progress_indicator_image = create_message_image(wsi->get_device(), 0, message_font, message.c_str()); } void achievement_progress_remove() { achievement_progress_indicator_image = Vulkan::ImageHandle(); } void leaderboard_tracker_add(uint32_t id, const char *title, const char *display) { if (title) { leaderboard_trackers[id] = std::format("{}: {}", title, display); update_challenge_indicator(); } } void leaderboard_tracker_remove(uint32_t id) { leaderboard_trackers.erase(id); update_challenge_indicator(); }