#include "rsx/rsx_lib_vulkan.h" #include #include #include #include "rsx/rsx_intf.h" //FPS and audio sample rate macros #include "parallel-psx/renderer/renderer.hpp" #include "libretro_vulkan.h" // #include "mednafen/mednafen.h" is required // for #include "mednafen/psx/gpu.h" to work properly. #include "mednafen/mednafen.h" #include "mednafen/psx/gpu.h" #include "libretro_cbs.h" #include "libretro_options.h" using namespace Vulkan; using namespace PSX; using namespace std; static Context *context; static Device *device; static Renderer *renderer; static unsigned scaling = 4; // Declare extern as workaround for now to avoid variable // naming conflicts with beetle_psx_globals.h extern "C" uint8_t widescreen_hack; extern "C" uint8_t widescreen_hack_aspect_ratio_setting; extern "C" bool content_is_pal; extern "C" int filter_mode; extern "C" bool currently_interlaced; extern "C" int aspect_ratio_setting; extern retro_log_printf_t log_cb; namespace Granite { retro_log_printf_t libretro_log; } static retro_hw_render_callback hw_render; static const struct retro_hw_render_interface_vulkan *vulkan; static retro_vulkan_image swapchain_image; static Renderer::SaveState save_state; static bool inside_frame; static bool has_software_fb; static bool scaled_uv_offset; static int filter_exclude_sprites; static int filter_exclude_2d_polygons; static bool adaptive_smoothing; static bool super_sampling; static unsigned msaa = 1; static bool mdec_yuv; static vector> defer; static dither_mode dither_mode = DITHER_NATIVE; static bool dump_textures = false; static bool replace_textures = false; static bool track_textures = false; static int crop_overscan; static int image_offset_cycles; static unsigned image_crop; static int initial_scanline; static int last_scanline; static int initial_scanline_pal; static int last_scanline_pal; static bool frame_duping_enabled = false; static uint32_t prev_frame_width = 320; static uint32_t prev_frame_height = 240; static bool show_vram = false; static retro_video_refresh_t video_refresh_cb; static const VkApplicationInfo *get_application_info(void) { static const VkApplicationInfo info = { VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "Beetle PSX", 0, "parallel-psx", 0, VK_MAKE_VERSION(1, 0, 32), }; return &info; } static void vk_context_reset(void) { if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&vulkan) || !vulkan) return; if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) { vulkan = nullptr; return; } assert(context); device = new Device; device->set_context(*context); renderer = new Renderer(*device, scaling, msaa, save_state.vram.empty() ? nullptr : &save_state); for (auto &func : defer) func(); defer.clear(); renderer->flush(); } static void vk_context_destroy(void) { save_state = renderer->save_vram_state(); vulkan = nullptr; delete renderer; delete device; delete context; renderer = nullptr; device = nullptr; context = nullptr; } static bool libretro_create_device( struct retro_vulkan_context *libretro_context, VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char **required_device_extensions, unsigned num_required_device_extensions, const char **required_device_layers, unsigned num_required_device_layers, const VkPhysicalDeviceFeatures *required_features) { if (!Vulkan::Context::init_loader(get_instance_proc_addr)) return false; if (context) { delete context; context = nullptr; } try { context = new Vulkan::Context(instance, gpu, surface, required_device_extensions, num_required_device_extensions, required_device_layers, num_required_device_layers, required_features); } catch (const std::exception &) { return false; } context->set_notification_callback([](const char* message) { printf("Vulkan Validation Layer Says: %s\n", message); }); context->release_device(); libretro_context->gpu = context->get_gpu(); libretro_context->device = context->get_device(); libretro_context->presentation_queue = context->get_graphics_queue(); libretro_context->presentation_queue_family_index = context->get_graphics_queue_family(); libretro_context->queue = context->get_graphics_queue(); libretro_context->queue_family_index = context->get_graphics_queue_family(); return true; } bool rsx_vulkan_open(bool is_pal) { Granite::libretro_log = log_cb; content_is_pal = is_pal; hw_render.context_type = RETRO_HW_CONTEXT_VULKAN; hw_render.version_major = VK_MAKE_VERSION(1, 0, 32); hw_render.version_minor = 0; hw_render.context_reset = vk_context_reset; hw_render.context_destroy = vk_context_destroy; hw_render.cache_context = false; if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) return false; static const struct retro_hw_render_context_negotiation_interface_vulkan iface = { RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, get_application_info, libretro_create_device, nullptr, }; environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void*)&iface); return true; } void rsx_vulkan_set_environment(retro_environment_t cb) { environ_cb = cb; } void rsx_vulkan_set_video_refresh(retro_video_refresh_t cb) { video_refresh_cb = cb; } void rsx_vulkan_get_system_av_info(struct retro_system_av_info *info) { rsx_vulkan_refresh_variables(); memset(info, 0, sizeof(*info)); // Set retro_game_geometry info->geometry.base_width = MEDNAFEN_CORE_GEOMETRY_BASE_W; info->geometry.base_height = MEDNAFEN_CORE_GEOMETRY_BASE_H; info->geometry.max_width = MEDNAFEN_CORE_GEOMETRY_MAX_W * (super_sampling ? 1 : scaling); info->geometry.max_height = MEDNAFEN_CORE_GEOMETRY_MAX_H * (super_sampling ? 1 : scaling); info->geometry.aspect_ratio = rsx_common_get_aspect_ratio(content_is_pal, crop_overscan, content_is_pal ? initial_scanline_pal : initial_scanline, content_is_pal ? last_scanline_pal : last_scanline, aspect_ratio_setting, show_vram, widescreen_hack, widescreen_hack_aspect_ratio_setting); // Set retro_system_timing info->timing.fps = rsx_common_get_timing_fps(); info->timing.sample_rate = SOUND_FREQUENCY; } void rsx_vulkan_refresh_variables(void) { struct retro_variable var = {0}; var.key = BEETLE_OPT(renderer_software_fb); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) has_software_fb = true; else has_software_fb = false; } else /* If 'BEETLE_OPT(renderer_software_fb)' option is not found, then * we are running in software mode */ has_software_fb = true; unsigned old_scaling = scaling; unsigned old_msaa = msaa; bool old_super_sampling = super_sampling; bool old_show_vram = show_vram; int old_crop_overscan = crop_overscan; unsigned old_image_crop = image_crop; bool old_widescreen_hack = widescreen_hack; unsigned old_widescreen_hack_aspect_ratio_setting = widescreen_hack_aspect_ratio_setting; bool visible_scanlines_changed = false; var.key = BEETLE_OPT(internal_resolution); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { /* Same limitations as libretro.cpp */ scaling = var.value[0] - '0'; if (var.value[1] != 'x') { scaling = (var.value[0] - '0') * 10; scaling += var.value[1] - '0'; } } var.key = BEETLE_OPT(scaled_uv_offset); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) scaled_uv_offset = true; else scaled_uv_offset = false; } var.key = BEETLE_OPT(filter_exclude_sprite); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "all")) filter_exclude_sprites = 2; else if (!strcmp(var.value, "opaque")) filter_exclude_sprites = 1; else filter_exclude_sprites = 0; } var.key = BEETLE_OPT(filter_exclude_2d_polygon); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "all")) filter_exclude_2d_polygons = 2; else if (!strcmp(var.value, "opaque")) filter_exclude_2d_polygons = 1; else filter_exclude_2d_polygons = 0; } var.key = BEETLE_OPT(adaptive_smoothing); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) adaptive_smoothing = true; else adaptive_smoothing = false; } var.key = BEETLE_OPT(super_sampling); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) super_sampling = true; else super_sampling = false; } var.key = BEETLE_OPT(msaa); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { msaa = strtoul(var.value, nullptr, 0); } var.key = BEETLE_OPT(mdec_yuv); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) mdec_yuv = true; else mdec_yuv = false; } var.key = BEETLE_OPT(dither_mode); dither_mode = DITHER_NATIVE; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "internal resolution")) dither_mode = DITHER_UPSCALED; else if (!strcmp(var.value, "disabled")) dither_mode = DITHER_OFF; } var.key = BEETLE_OPT(crop_overscan); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "disabled") == 0) crop_overscan = 0; else if (strcmp(var.value, "static") == 0) crop_overscan = 1; else if (strcmp(var.value, "smart") == 0) crop_overscan = 2; } var.key = BEETLE_OPT(image_offset_cycles); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { image_offset_cycles = atoi(var.value); } var.key = BEETLE_OPT(image_crop); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "disabled") == 0) image_crop = 0; else image_crop = atoi(var.value); } var.key = BEETLE_OPT(initial_scanline); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int new_initial_scanline = atoi(var.value); if (new_initial_scanline != initial_scanline) { initial_scanline = new_initial_scanline; visible_scanlines_changed = true; } } var.key = BEETLE_OPT(last_scanline); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int new_last_scanline = atoi(var.value); if (new_last_scanline != last_scanline) { last_scanline = new_last_scanline; visible_scanlines_changed = true; } } var.key = BEETLE_OPT(initial_scanline_pal); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int new_initial_scanline_pal = atoi(var.value); if (new_initial_scanline_pal != initial_scanline_pal) { initial_scanline_pal = new_initial_scanline_pal; visible_scanlines_changed = true; } } var.key = BEETLE_OPT(last_scanline_pal); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int new_last_scanline_pal = atoi(var.value); if (new_last_scanline_pal != last_scanline_pal) { last_scanline_pal = new_last_scanline_pal; visible_scanlines_changed = true; } } var.key = BEETLE_OPT(widescreen_hack); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) widescreen_hack = true; else widescreen_hack = false; } var.key = BEETLE_OPT(widescreen_hack_aspect_ratio); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "16:10")) widescreen_hack_aspect_ratio_setting = 0; else if (!strcmp(var.value, "16:9")) widescreen_hack_aspect_ratio_setting = 1; else if (!strcmp(var.value, "18:9")) widescreen_hack_aspect_ratio_setting = 2; else if (!strcmp(var.value, "19:9")) widescreen_hack_aspect_ratio_setting = 3; else if (!strcmp(var.value, "20:9")) widescreen_hack_aspect_ratio_setting = 4; else if (!strcmp(var.value, "21:9")) widescreen_hack_aspect_ratio_setting = 5; else if (!strcmp(var.value, "32:9")) widescreen_hack_aspect_ratio_setting = 6; } var.key = BEETLE_OPT(track_textures); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) track_textures = true; else track_textures = false; } var.key = BEETLE_OPT(dump_textures); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) dump_textures = true; else dump_textures = false; } var.key = BEETLE_OPT(replace_textures); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) replace_textures = true; else replace_textures = false; } struct retro_core_option_display option_display; option_display.visible = track_textures; option_display.key = BEETLE_OPT(dump_textures); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = BEETLE_OPT(replace_textures); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); var.key = BEETLE_OPT(frame_duping); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) { bool frontend_can_dupe = false; if (environ_cb(RETRO_ENVIRONMENT_GET_CAN_DUPE, &frontend_can_dupe)) { frame_duping_enabled = frontend_can_dupe; if (!frontend_can_dupe) log_cb(RETRO_LOG_INFO, "Frontend does not support frame duping. Frame duping will be disabled.\n"); } } else frame_duping_enabled = false; } var.key = BEETLE_OPT(display_vram); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) show_vram = true; else show_vram = false; } // Clean this up. Possible to categorize by order of severity, e.g. geometry dirty flag vs full system_av dirty flag if ((old_scaling != scaling || old_super_sampling != super_sampling || old_msaa != msaa || old_show_vram != show_vram || old_crop_overscan != crop_overscan || old_image_crop != image_crop || old_widescreen_hack != widescreen_hack || old_widescreen_hack_aspect_ratio_setting != widescreen_hack_aspect_ratio_setting || visible_scanlines_changed) && renderer) { // Potential bad behavior from calling rsx_vulkan_get_system_av_info() from inside // rsx_vulkan_refresh_variables() since both functions call each other... retro_system_av_info info; rsx_vulkan_get_system_av_info(&info); if (!environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info)) { // Failed to change scale, just keep using the old one. scaling = old_scaling; } } } void rsx_vulkan_prepare_frame(void) { inside_frame = true; device->flush_frame(); vulkan->wait_sync_index(vulkan->handle); unsigned index = vulkan->get_sync_index(vulkan->handle); device->next_frame_context(); renderer->reset_counters(); renderer->set_scaled_uv_offset(scaled_uv_offset); renderer->set_filter_mode(static_cast(filter_mode)); renderer->set_sprite_filter_exclude(static_cast(filter_exclude_sprites)); renderer->set_polygon_2d_filter_exclude(static_cast(filter_exclude_2d_polygons)); } static Renderer::ScanoutMode get_scanout_mode(bool bpp24) { if (bpp24) return Renderer::ScanoutMode::BGR24; else if (dither_mode != DITHER_OFF) return Renderer::ScanoutMode::ABGR1555_Dither; else return Renderer::ScanoutMode::ABGR1555_555; } void rsx_vulkan_finalize_frame(const void *fb, unsigned width, unsigned height, unsigned pitch) { if (frame_duping_enabled && !GPU_get_display_change_count()) { /* Any visual core option changes will be deferred to next non-duped frame */ //printf("No PSX GPU display update; duping frame\n"); renderer->flush(); video_refresh_cb(NULL, prev_frame_width, prev_frame_height, 0); inside_frame = false; return; } renderer->set_track_textures(track_textures); renderer->set_dump_textures(dump_textures); renderer->set_replace_textures(replace_textures); renderer->set_adaptive_smoothing(adaptive_smoothing); renderer->set_dither_native_resolution(dither_mode == DITHER_NATIVE); renderer->set_horizontal_overscan_cropping(crop_overscan); renderer->set_horizontal_offset_cycles(image_offset_cycles); renderer->set_visible_scanlines(initial_scanline, last_scanline, initial_scanline_pal, last_scanline_pal); renderer->set_horizontal_additional_cropping(image_crop); renderer->set_display_filter(super_sampling ? Renderer::ScanoutFilter::SSAA : Renderer::ScanoutFilter::None); if (renderer->get_scanout_mode() == Renderer::ScanoutMode::BGR24) renderer->set_mdec_filter(mdec_yuv ? Renderer::ScanoutFilter::MDEC_YUV : Renderer::ScanoutFilter::None); else renderer->set_mdec_filter(Renderer::ScanoutFilter::None); auto scanout = show_vram ? renderer->scanout_vram_to_texture() : renderer->scanout_to_texture(); retro_vulkan_image *image = &swapchain_image; image->create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image->create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image->create_info.format = scanout->get_format(); image->create_info.subresourceRange.baseMipLevel = 0; image->create_info.subresourceRange.baseArrayLayer = 0; image->create_info.subresourceRange.levelCount = 1; image->create_info.subresourceRange.layerCount = 1; image->create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image->create_info.components.r = VK_COMPONENT_SWIZZLE_R; image->create_info.components.g = VK_COMPONENT_SWIZZLE_G; image->create_info.components.b = VK_COMPONENT_SWIZZLE_B; image->create_info.components.a = VK_COMPONENT_SWIZZLE_A; image->create_info.image = scanout->get_image(); image->image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; image->image_view = scanout->get_view().get_view(); vulkan->set_image(vulkan->handle, image, 0, nullptr, VK_QUEUE_FAMILY_IGNORED); renderer->flush(); auto semaphore = device->request_semaphore(); vulkan->set_signal_semaphore(vulkan->handle, semaphore->get_semaphore()); semaphore->signal_external(); renderer->set_scanout_semaphore(semaphore); video_refresh_cb(RETRO_HW_FRAME_BUFFER_VALID, scanout->get_width(), scanout->get_height(), 0); inside_frame = false; prev_frame_width = scanout->get_width(); prev_frame_height = scanout ->get_height(); #if 0 printf("%d %d\n", scanout->get_width(), scanout->get_height()); fprintf(stderr, "Render passes: %u, Readback: %u, Writeout: %u\n", renderer->counters.render_passes, renderer->counters.fragment_readback_pixels, renderer->counters.fragment_writeout_pixels); #endif } /* Draw commands */ void rsx_vulkan_set_tex_window(uint8_t tww, uint8_t twh, uint8_t twx, uint8_t twy) { uint8_t tex_x_mask = ~(tww << 3); uint8_t tex_y_mask = ~(twh << 3); uint8_t tex_x_or = (twx & tww) << 3; uint8_t tex_y_or = (twy & twh) << 3; if (renderer) renderer->set_texture_window({ tex_x_mask, tex_y_mask, tex_x_or, tex_y_or }); else { defer.push_back([=]() { renderer->set_texture_window({ tex_x_mask, tex_y_mask, tex_x_or, tex_y_or}); }); } } void rsx_vulkan_set_draw_offset(int16_t x, int16_t y) { if (renderer) renderer->set_draw_offset(x, y); else { defer.push_back([=]() { renderer->set_draw_offset(x, y); }); } } void rsx_vulkan_set_draw_area(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { int width = x1 - x0 + 1; int height = y1 - y0 + 1; width = max(width, 0); height = max(height, 0); width = min(width, int(FB_WIDTH - x0)); height = min(height, int(FB_HEIGHT - y0)); if (renderer) renderer->set_draw_rect({ x0, y0, unsigned(width), unsigned(height) }); else { defer.push_back([=]() { renderer->set_draw_rect({ x0, y0, unsigned(width), unsigned(height) }); }); } } void rsx_vulkan_set_vram_framebuffer_coords(uint32_t xstart, uint32_t ystart) { if (renderer) renderer->set_vram_framebuffer_coords(xstart, ystart); else { defer.push_back([=]() { renderer->set_vram_framebuffer_coords(xstart, ystart); }); } } void rsx_vulkan_set_horizontal_display_range(uint16_t x1, uint16_t x2) { if (renderer) renderer->set_horizontal_display_range(x1, x2); else { defer.push_back([=]() { renderer->set_horizontal_display_range(x1, x2); }); } } void rsx_vulkan_set_vertical_display_range(uint16_t y1, uint16_t y2) { if (renderer) renderer->set_vertical_display_range(y1, y2); else { defer.push_back([=]() { renderer->set_vertical_display_range(y1, y2); }); } } void rsx_vulkan_set_display_mode(bool depth_24bpp, bool is_pal, bool is_480i, int width_mode) { if (renderer) renderer->set_display_mode(get_scanout_mode(depth_24bpp), is_pal, is_480i, static_cast(width_mode)); else { defer.push_back([=]() { renderer->set_display_mode(get_scanout_mode(depth_24bpp), is_pal, is_480i, static_cast(width_mode)); }); } } void rsx_vulkan_push_triangle( float p0x, float p0y, float p0w, float p1x, float p1y, float p1w, float p2x, float p2y, float p2w, uint32_t c0, uint32_t c1, uint32_t c2, uint16_t t0x, uint16_t t0y, uint16_t t1x, uint16_t t1y, uint16_t t2x, uint16_t t2y, uint16_t min_u, uint16_t min_v, uint16_t max_u, uint16_t max_v, uint16_t texpage_x, uint16_t texpage_y, uint16_t clut_x, uint16_t clut_y, uint8_t texture_blend_mode, uint8_t depth_shift, bool dither, int blend_mode, bool mask_test, bool set_mask) { if (!renderer) return; renderer->set_texture_color_modulate(texture_blend_mode == 2); renderer->set_palette_offset(clut_x, clut_y); renderer->set_texture_offset(texpage_x, texpage_y); //renderer->set_dither(dither); renderer->set_mask_test(mask_test); renderer->set_force_mask_bit(set_mask); renderer->set_UV_limits(min_u, min_v, max_u, max_v); if (texture_blend_mode != 0) { switch (depth_shift) { default: case 0: renderer->set_texture_mode(TextureMode::ABGR1555); break; case 1: renderer->set_texture_mode(TextureMode::Palette8bpp); break; case 2: renderer->set_texture_mode(TextureMode::Palette4bpp); break; } } else renderer->set_texture_mode(TextureMode::None); switch (blend_mode) { default: renderer->set_semi_transparent(SemiTransparentMode::None); break; case 0: renderer->set_semi_transparent(SemiTransparentMode::Average); break; case 1: renderer->set_semi_transparent(SemiTransparentMode::Add); break; case 2: renderer->set_semi_transparent(SemiTransparentMode::Sub); break; case 3: renderer->set_semi_transparent(SemiTransparentMode::AddQuarter); break; } renderer->set_primitive_type(PrimitiveType::Polygon); Vertex vertices[3] = { { p0x, p0y, p0w, c0, t0x, t0y }, { p1x, p1y, p1w, c1, t1x, t1y }, { p2x, p2y, p2w, c2, t2x, t2y }, }; renderer->draw_triangle(vertices); } void rsx_vulkan_push_quad( float p0x, float p0y, float p0w, float p1x, float p1y, float p1w, float p2x, float p2y, float p2w, float p3x, float p3y, float p3w, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3, uint16_t t0x, uint16_t t0y, uint16_t t1x, uint16_t t1y, uint16_t t2x, uint16_t t2y, uint16_t t3x, uint16_t t3y, uint16_t min_u, uint16_t min_v, uint16_t max_u, uint16_t max_v, uint16_t texpage_x, uint16_t texpage_y, uint16_t clut_x, uint16_t clut_y, uint8_t texture_blend_mode, uint8_t depth_shift, bool dither, int blend_mode, bool mask_test, bool set_mask, bool is_sprite, bool may_be_2d) { if (!renderer) return; renderer->set_texture_color_modulate(texture_blend_mode == 2); renderer->set_palette_offset(clut_x, clut_y); renderer->set_texture_offset(texpage_x, texpage_y); //renderer->set_dither(dither); renderer->set_mask_test(mask_test); renderer->set_force_mask_bit(set_mask); renderer->set_UV_limits(min_u, min_v, max_u, max_v); if (texture_blend_mode != 0) { switch (depth_shift) { default: case 0: renderer->set_texture_mode(TextureMode::ABGR1555); break; case 1: renderer->set_texture_mode(TextureMode::Palette8bpp); break; case 2: renderer->set_texture_mode(TextureMode::Palette4bpp); break; } } else renderer->set_texture_mode(TextureMode::None); switch (blend_mode) { default: renderer->set_semi_transparent(SemiTransparentMode::None); break; case 0: renderer->set_semi_transparent(SemiTransparentMode::Average); break; case 1: renderer->set_semi_transparent(SemiTransparentMode::Add); break; case 2: renderer->set_semi_transparent(SemiTransparentMode::Sub); break; case 3: renderer->set_semi_transparent(SemiTransparentMode::AddQuarter); break; } if (is_sprite) renderer->set_primitive_type(PrimitiveType::Sprite); else if (may_be_2d) renderer->set_primitive_type(PrimitiveType::May_Be_2D_Polygon); else renderer->set_primitive_type(PrimitiveType::Polygon); Vertex vertices[4] = { { p0x, p0y, p0w, c0, t0x, t0y }, { p1x, p1y, p1w, c1, t1x, t1y }, { p2x, p2y, p2w, c2, t2x, t2y }, { p3x, p3y, p3w, c3, t3x, t3y }, }; renderer->draw_quad(vertices); } void rsx_vulkan_push_line( int16_t p0x, int16_t p0y, int16_t p1x, int16_t p1y, uint32_t c0, uint32_t c1, bool dither, int blend_mode, bool mask_test, bool set_mask) { if (!renderer) return; renderer->set_texture_mode(TextureMode::None); renderer->set_mask_test(mask_test); renderer->set_force_mask_bit(set_mask); switch (blend_mode) { default: renderer->set_semi_transparent(SemiTransparentMode::None); break; case 0: renderer->set_semi_transparent(SemiTransparentMode::Average); break; case 1: renderer->set_semi_transparent(SemiTransparentMode::Add); break; case 2: renderer->set_semi_transparent(SemiTransparentMode::Sub); break; case 3: renderer->set_semi_transparent(SemiTransparentMode::AddQuarter); break; } Vertex vertices[2] = { { float(p0x), float(p0y), 1.0f, c0, 0, 0 }, { float(p1x), float(p1y), 1.0f, c1, 0, 0 }, }; //renderer->set_dither(dither); renderer->set_texture_color_modulate(false); renderer->draw_line(vertices); } void rsx_vulkan_load_image( uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *vram, bool mask_test, bool set_mask) { #ifndef NDEBUG TT_LOG_VERBOSE(RETRO_LOG_INFO, "rsx_vulkan_load_image(x=%i, y=%i, w=%i, h=%i, mask_test=%i, set_mask=%i).\n", x, y, w, h, mask_test, set_mask); #endif if (!renderer) { // Generally happens if someone loads a save state before the Vulkan context is created. defer.push_back([=]() { rsx_vulkan_load_image(x, y, w, h, vram, mask_test, set_mask); }); return; } renderer->notify_texture_upload(PSX::Rect { x, y, w, h }, vram); bool dual_copy = x + w > FB_WIDTH; // Check if we need to handle wrap-around in X. renderer->set_mask_test(mask_test); renderer->set_force_mask_bit(set_mask); auto handle = renderer->copy_cpu_to_vram({ x, y, w, h }); uint16_t *tmp = renderer->begin_copy(handle); for (unsigned off_y = 0; off_y < h; off_y++) { if (dual_copy) { unsigned first = FB_WIDTH - x; unsigned second = w - first; memcpy(tmp + off_y * w, vram + ((y + off_y) & (FB_HEIGHT - 1)) * FB_WIDTH + x, first * sizeof(uint16_t)); memcpy(tmp + off_y * w + first, vram + ((y + off_y) & (FB_HEIGHT - 1)) * FB_WIDTH, second * sizeof(uint16_t)); } else { memcpy(tmp + off_y * w, vram + ((y + off_y) & (FB_HEIGHT - 1)) * FB_WIDTH + x, w * sizeof(uint16_t)); } } renderer->end_copy(handle); // This is called on state loading. if (!inside_frame) renderer->flush(); } bool rsx_vulkan_read_vram(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *vram) { if (!renderer) return false; renderer->copy_vram_to_cpu_synchronous({ x, y, w, h }, vram); return true; } void rsx_vulkan_fill_rect(uint32_t color, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { if (renderer) renderer->clear_rect({ x, y, w, h }, color); } void rsx_vulkan_copy_rect(uint16_t src_x, uint16_t src_y, uint16_t dst_x, uint16_t dst_y, uint16_t w, uint16_t h, bool mask_test, bool set_mask) { if (!renderer) return; renderer->set_mask_test(mask_test); renderer->set_force_mask_bit(set_mask); renderer->blit_vram({ dst_x, dst_y, w, h }, { src_x, src_y, w, h }); } void rsx_vulkan_toggle_display(bool status) { if (renderer) renderer->toggle_display(status == 0); else { defer.push_back([=] { renderer->toggle_display(status == 0); }); } } bool rsx_vulkan_has_software_renderer(void) { return has_software_fb; }