diff --git a/rpcs3/Emu/RSX/Common/GLSLCommon.h b/rpcs3/Emu/RSX/Common/GLSLCommon.h index 05992a83a6..de26e385de 100644 --- a/rpcs3/Emu/RSX/Common/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Common/GLSLCommon.h @@ -83,7 +83,8 @@ namespace glsl enum program_domain { glsl_vertex_program = 0, - glsl_fragment_program = 1 + glsl_fragment_program = 1, + glsl_compute_program = 2 }; enum glsl_rules diff --git a/rpcs3/Emu/RSX/VK/VKCommonDecompiler.cpp b/rpcs3/Emu/RSX/VK/VKCommonDecompiler.cpp index ecdb5b99cf..daa1dc6146 100644 --- a/rpcs3/Emu/RSX/VK/VKCommonDecompiler.cpp +++ b/rpcs3/Emu/RSX/VK/VKCommonDecompiler.cpp @@ -140,7 +140,8 @@ namespace vk bool compile_glsl_to_spv(std::string& shader, program_domain domain, std::vector& spv) { - EShLanguage lang = (domain == glsl_fragment_program) ? EShLangFragment : EShLangVertex; + EShLanguage lang = (domain == glsl_fragment_program) ? EShLangFragment : + (domain == glsl_vertex_program)? EShLangVertex : EShLangCompute; glslang::TProgram program; glslang::TShader shader_object(lang); diff --git a/rpcs3/Emu/RSX/VK/VKCompute.h b/rpcs3/Emu/RSX/VK/VKCompute.h new file mode 100644 index 0000000000..58ce64e4cb --- /dev/null +++ b/rpcs3/Emu/RSX/VK/VKCompute.h @@ -0,0 +1,256 @@ +#pragma once +#include "VKHelpers.h" + +namespace vk +{ + struct compute_task + { + std::string m_src; + vk::glsl::shader m_shader; + std::unique_ptr m_program; + + vk::descriptor_pool m_descriptor_pool; + VkDescriptorSet m_descriptor_set = nullptr; + VkDescriptorSetLayout m_descriptor_layout = nullptr; + VkPipelineLayout m_pipeline_layout = nullptr; + u32 m_used_descriptors = 0; + + bool initialized = false; + u32 optimal_group_size = 64; + + void init_descriptors() + { + VkDescriptorPoolSize descriptor_pool_sizes[1] = + { + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 120 }, + }; + + //Reserve descriptor pools + m_descriptor_pool.create(*get_current_renderer(), descriptor_pool_sizes, 1); + + std::vector bindings(1); + + bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[0].descriptorCount = 1; + bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[0].binding = 0; + bindings[0].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo infos = {}; + infos.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + infos.pBindings = bindings.data(); + infos.bindingCount = bindings.size(); + + CHECK_RESULT(vkCreateDescriptorSetLayout(*get_current_renderer(), &infos, nullptr, &m_descriptor_layout)); + + VkPipelineLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = &m_descriptor_layout; + + CHECK_RESULT(vkCreatePipelineLayout(*get_current_renderer(), &layout_info, nullptr, &m_pipeline_layout)); + } + + void create() + { + if (!initialized) + { + init_descriptors(); + + switch (vk::get_driver_vendor()) + { + case vk::driver_vendor::unknown: + // Probably intel + case vk::driver_vendor::NVIDIA: + optimal_group_size = 32; + break; + } + + initialized = true; + } + } + + void destroy() + { + if (initialized) + { + m_shader.destroy(); + m_program.reset(); + + vkDestroyDescriptorSetLayout(*get_current_renderer(), m_descriptor_layout, nullptr); + vkDestroyPipelineLayout(*get_current_renderer(), m_pipeline_layout, nullptr); + m_descriptor_pool.destroy(); + + initialized = false; + } + } + + void free_resources() + { + if (m_used_descriptors == 0) + return; + + vkResetDescriptorPool(*get_current_renderer(), m_descriptor_pool, 0); + m_used_descriptors = 0; + } + + virtual void bind_resources() + {} + + void load_program(const vk::command_buffer& cmd) + { + if (!m_program) + { + m_shader.create(::glsl::program_domain::glsl_compute_program, m_src); + auto handle = m_shader.compile(); + + VkPipelineShaderStageCreateInfo shader_stage{}; + shader_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shader_stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + shader_stage.module = handle; + shader_stage.pName = "main"; + + VkComputePipelineCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + info.stage = shader_stage; + info.layout = m_pipeline_layout; + info.basePipelineIndex = -1; + info.basePipelineHandle = VK_NULL_HANDLE; + + VkPipeline pipeline; + vkCreateComputePipelines(*get_current_renderer(), nullptr, 1, &info, nullptr, &pipeline); + + std::vector inputs; + m_program = std::make_unique(*get_current_renderer(), pipeline, inputs, inputs); + } + + verify(HERE), m_used_descriptors < 120; + + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.descriptorPool = m_descriptor_pool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &m_descriptor_layout; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + + CHECK_RESULT(vkAllocateDescriptorSets(*get_current_renderer(), &alloc_info, &m_descriptor_set)); + m_used_descriptors++; + + bind_resources(); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_program->pipeline); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_pipeline_layout, 0, 1, &m_descriptor_set, 0, nullptr); + } + + virtual void run(const vk::command_buffer& cmd, u32 num_invocations) + { + load_program(cmd); + vkCmdDispatch(cmd, num_invocations, 1, 1); + } + }; + + struct cs_shuffle_base : compute_task + { + vk::buffer* m_data; + u32 kernel_size = 1; + + void build(const char* function_name, u32 _kernel_size) + { + kernel_size = _kernel_size; + + m_src = + { + "#version 430\n" + "layout(local_size_x=%ws, local_size_y=1, local_size_z=1) in;\n" + "layout(std430, set=0, binding=0) buffer ssbo{ uint data[]; };\n\n" + "\n" + "#define KERNEL_SIZE %ks\n" + "#define bswap_u16(bits) (bits & 0xFF) << 8 | (bits & 0xFF00) >> 8 | (bits & 0xFF0000) << 8 | (bits & 0xFF000000) >> 8\n" + "#define bswap_u32(bits) (bits & 0xFF) << 24 | (bits & 0xFF00) << 8 | (bits & 0xFF0000) >> 8 | (bits & 0xFF000000) >> 24\n" + "#define bswap_u16_u32(bits) (bits & 0xFFFF) << 16 | (bits & 0xFFFF0000) >> 16\n" + "\n" + "void main()\n" + "{\n" + " uint index = gl_GlobalInvocationID.x * KERNEL_SIZE;\n" + " for (uint loop = 0; loop < KERNEL_SIZE; ++loop)\n" + " {\n" + " uint value = data[index];\n" + " data[index] = %f(value);\n" + " index++;\n" + " }\n" + "}\n" + }; + + const std::pair syntax_replace[] = + { + { "%ws", std::to_string(optimal_group_size) }, + { "%ks", std::to_string(kernel_size) }, + { "%f", function_name } + }; + + m_src = fmt::replace_all(m_src, syntax_replace); + } + + void bind_resources() override + { + m_program->bind_buffer({ m_data->value, 0, VK_WHOLE_SIZE }, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, m_descriptor_set); + } + + void run(const vk::command_buffer& cmd, vk::buffer* data, u32 mem_size) + { + m_data = data; + + const auto num_bytes_per_invocation = optimal_group_size * kernel_size * 4; + const auto num_invocations = align(mem_size, 256) / num_bytes_per_invocation; + compute_task::run(cmd, num_invocations); + } + }; + + struct cs_shuffle_16 : cs_shuffle_base + { + vk::buffer* m_data; + + // byteswap ushort + cs_shuffle_16() + { + cs_shuffle_base::build("bswap_u16", 32); + } + }; + + struct cs_shuffle_32 : cs_shuffle_base + { + // byteswap_ulong + cs_shuffle_32() + { + cs_shuffle_base::build("bswap_u32", 32); + } + }; + + struct cs_shuffle_32_16 : cs_shuffle_base + { + // byteswap_ulong + byteswap_ushort + cs_shuffle_32_16() + { + cs_shuffle_base::build("bswap_u16_u32", 32); + } + }; + + // TODO: Replace with a proper manager + extern std::unordered_map> g_compute_tasks; + + template + T* get_compute_task() + { + u32 index = id_manager::typeinfo::get_index(); + auto &e = g_compute_tasks[index]; + + if (!e) + { + e = std::make_unique(); + e->create(); + } + + return static_cast(e.get()); + } + + void reset_compute_tasks(); +} diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index 675ee3a46a..854eeab0eb 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -362,8 +362,11 @@ VKFragmentProgram::~VKFragmentProgram() void VKFragmentProgram::Decompile(const RSXFragmentProgram& prog) { u32 size; - VKFragmentDecompilerThread decompiler(shader, parr, prog, size, *this); + std::string source; + VKFragmentDecompilerThread decompiler(source, parr, prog, size, *this); decompiler.Task(); + + shader.create(::glsl::program_domain::glsl_fragment_program, source); for (const ParamType& PT : decompiler.m_parr.params[PF_PARAM_UNIFORM]) { @@ -384,34 +387,13 @@ void VKFragmentProgram::Decompile(const RSXFragmentProgram& prog) void VKFragmentProgram::Compile() { fs::create_path(fs::get_config_dir() + "/shaderlog"); - fs::file(fs::get_config_dir() + "shaderlog/FragmentProgram" + std::to_string(id) + ".spirv", fs::rewrite).write(shader); - - std::vector spir_v; - if (!vk::compile_glsl_to_spv(shader, glsl::glsl_fragment_program, spir_v)) - fmt::throw_exception("Failed to compile fragment shader" HERE); - - //Create the object and compile - VkShaderModuleCreateInfo fs_info; - fs_info.codeSize = spir_v.size() * sizeof(u32); - fs_info.pNext = nullptr; - fs_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - fs_info.pCode = (uint32_t*)spir_v.data(); - fs_info.flags = 0; - - VkDevice dev = (VkDevice)*vk::get_current_renderer(); - vkCreateShaderModule(dev, &fs_info, nullptr, &handle); + fs::file(fs::get_config_dir() + "shaderlog/FragmentProgram" + std::to_string(id) + ".spirv", fs::rewrite).write(shader.get_source()); + handle = shader.compile(); } void VKFragmentProgram::Delete() { - shader.clear(); - - if (handle) - { - VkDevice dev = (VkDevice)*vk::get_current_renderer(); - vkDestroyShaderModule(dev, handle, NULL); - handle = nullptr; - } + shader.destroy(); } void VKFragmentProgram::SetInputs(std::vector& inputs) diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h index d427edf2f5..6d75e021e8 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h @@ -49,7 +49,7 @@ public: ParamArray parr; VkShaderModule handle = nullptr; u32 id; - std::string shader; + vk::glsl::shader shader; std::vector FragmentConstantOffsetCache; std::array output_color_masks{ {} }; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index acdbbf0c9d..cea34a6894 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2024,6 +2024,8 @@ void VKGSRender::process_swap_request(frame_context_t *ctx, bool free_resources) m_overlay_manager->dispose(uids_to_dispose); } + vk::reset_compute_tasks(); + m_attachment_clear_pass->free_resources(); m_depth_converter->free_resources(); m_ui_renderer->free_resources(); diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.cpp b/rpcs3/Emu/RSX/VK/VKHelpers.cpp index 637d28f5e7..e9dbc0885e 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.cpp +++ b/rpcs3/Emu/RSX/VK/VKHelpers.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "VKHelpers.h" +#include "VKCompute.h" #include "Utilities/mutex.h" namespace vk @@ -11,6 +12,7 @@ namespace vk std::unique_ptr g_null_image_view; std::unique_ptr g_scratch_buffer; std::unordered_map> g_typeless_textures; + std::unordered_map> g_compute_tasks; VkSampler g_null_sampler = nullptr; @@ -195,7 +197,7 @@ namespace vk // 32M disposable scratch memory g_scratch_buffer = std::make_unique(*g_current_renderer, 32 * 0x100000, g_current_renderer->get_memory_mapping().device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0); + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 0); } return g_scratch_buffer.get(); @@ -211,6 +213,14 @@ namespace vk g_submit_mutex.unlock(); } + void reset_compute_tasks() + { + for (const auto &p : g_compute_tasks) + { + p.second->free_resources(); + } + } + void destroy_global_resources() { g_null_texture.reset(); @@ -223,6 +233,13 @@ namespace vk vkDestroySampler(*g_current_renderer, g_null_sampler, nullptr); g_null_sampler = nullptr; + + for (const auto& p : g_compute_tasks) + { + p.second->destroy(); + } + + g_compute_tasks.clear(); } vk::mem_allocator_base* get_current_mem_allocator() @@ -330,6 +347,21 @@ namespace vk return g_drv_disable_fence_reset; } + void insert_buffer_memory_barrier(VkCommandBuffer cmd, VkBuffer buffer, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, VkAccessFlags src_mask, VkAccessFlags dst_mask) + { + VkBufferMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + barrier.buffer = buffer; + barrier.offset = 0; + barrier.size = VK_WHOLE_SIZE; + barrier.srcAccessMask = src_mask; + barrier.dstAccessMask = dst_mask; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + + vkCmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, nullptr, 1, &barrier, 0, nullptr); + } + void change_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout current_layout, VkImageLayout new_layout, VkImageSubresourceRange range) { //Prepare an image to match the new layout.. diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index 40c01ed393..35c360049c 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -13,10 +13,10 @@ #include "Emu/RSX/GSRender.h" #include "Emu/System.h" #include "VulkanAPI.h" +#include "VKCommonDecompiler.h" #include "../GCM.h" #include "../Common/TextureUtils.h" #include "../Common/ring_buffer_helper.h" -#include "../Common/GLSLCommon.h" #include "../rsx_cache.h" #include "3rdparty/GPUOpen/include/vk_mem_alloc.h" @@ -110,6 +110,10 @@ namespace vk void acquire_global_submit_lock(); void release_global_submit_lock(); + template + T* get_compute_task(); + void reset_compute_tasks(); + void destroy_global_resources(); /** @@ -126,15 +130,15 @@ namespace vk void change_image_layout(VkCommandBuffer cmd, vk::image *image, VkImageLayout new_layout, VkImageSubresourceRange range); void change_image_layout(VkCommandBuffer cmd, vk::image *image, VkImageLayout new_layout); - void copy_image_typeless(VkCommandBuffer cmd, VkImage &src, VkImage &dst, VkImageLayout srcLayout, VkImageLayout dstLayout, - const areai& src_rect, const areai& dst_rect, u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, + void copy_image_typeless(const command_buffer &cmd, const image *src, const image *dst, const areai& src_rect, const areai& dst_rect, + u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, VkImageAspectFlags src_transfer_mask = 0xFF, VkImageAspectFlags dst_transfer_mask = 0xFF); - void copy_image(VkCommandBuffer cmd, VkImage &src, VkImage &dst, VkImageLayout srcLayout, VkImageLayout dstLayout, + void copy_image(VkCommandBuffer cmd, VkImage src, VkImage dst, VkImageLayout srcLayout, VkImageLayout dstLayout, const areai& src_rect, const areai& dst_rect, u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, VkImageAspectFlags src_transfer_mask = 0xFF, VkImageAspectFlags dst_transfer_mask = 0xFF); - void copy_scaled_image(VkCommandBuffer cmd, VkImage &src, VkImage &dst, VkImageLayout srcLayout, VkImageLayout dstLayout, + void copy_scaled_image(VkCommandBuffer cmd, VkImage src, VkImage dst, VkImageLayout srcLayout, VkImageLayout dstLayout, u32 src_x_offset, u32 src_y_offset, u32 src_width, u32 src_height, u32 dst_x_offset, u32 dst_y_offset, u32 dst_width, u32 dst_height, u32 mipmaps, VkImageAspectFlags aspect, bool compatible_formats, VkFilter filter = VK_FILTER_LINEAR, VkFormat src_format = VK_FORMAT_UNDEFINED, VkFormat dst_format = VK_FORMAT_UNDEFINED); @@ -145,6 +149,8 @@ namespace vk void insert_texture_barrier(VkCommandBuffer cmd, VkImage image, VkImageLayout layout, VkImageSubresourceRange range); void insert_texture_barrier(VkCommandBuffer cmd, vk::image *image); + void insert_buffer_memory_barrier(VkCommandBuffer cmd, VkBuffer buffer, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, VkAccessFlags src_mask, VkAccessFlags dst_mask); + //Manage 'uininterruptible' state where secondary operations (e.g violation handlers) will have to wait void enter_uninterruptible(); void leave_uninterruptible(); @@ -1021,7 +1027,7 @@ namespace vk return *pool; } - operator VkCommandBuffer() + operator VkCommandBuffer() const { return commands; } @@ -2116,13 +2122,13 @@ public: class descriptor_pool { VkDescriptorPool pool = nullptr; - vk::render_device *owner = nullptr; + const vk::render_device *owner = nullptr; public: descriptor_pool() {} ~descriptor_pool() {} - void create(vk::render_device &dev, VkDescriptorPoolSize *sizes, u32 size_descriptors_count) + void create(const vk::render_device &dev, VkDescriptorPoolSize *sizes, u32 size_descriptors_count) { VkDescriptorPoolCreateInfo infos = {}; infos.flags = 0; @@ -2426,7 +2432,8 @@ public: { input_type_uniform_buffer = 0, input_type_texel_buffer = 1, - input_type_texture = 2 + input_type_texture = 2, + input_type_storage_buffer = 3 }; struct bound_sampler @@ -2456,6 +2463,80 @@ public: std::string name; }; + class shader + { + ::glsl::program_domain type = ::glsl::program_domain::glsl_vertex_program; + VkShaderModule m_handle = VK_NULL_HANDLE; + std::string m_source; + std::vector m_compiled; + + public: + shader() + {} + + ~shader() + {} + + void create(::glsl::program_domain domain, const std::string& source) + { + type = domain; + m_source = source; + } + + VkShaderModule compile() + { + verify(HERE), m_handle == VK_NULL_HANDLE; + + if (!vk::compile_glsl_to_spv(m_source, type, m_compiled)) + { + std::string shader_type = type == ::glsl::program_domain::glsl_vertex_program ? "vertex" : + type == ::glsl::program_domain::glsl_fragment_program ? "fragment" : "compute"; + + fmt::throw_exception("Failed to compile %s shader" HERE, shader_type); + } + + VkShaderModuleCreateInfo vs_info; + vs_info.codeSize = m_compiled.size() * sizeof(u32); + vs_info.pNext = nullptr; + vs_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + vs_info.pCode = (uint32_t*)m_compiled.data(); + vs_info.flags = 0; + + VkDevice dev = (VkDevice)*vk::get_current_renderer(); + vkCreateShaderModule(dev, &vs_info, nullptr, &m_handle); + + return m_handle; + } + + void destroy() + { + m_source.clear(); + m_compiled.clear(); + + if (m_handle) + { + VkDevice dev = (VkDevice)*vk::get_current_renderer(); + vkDestroyShaderModule(dev, m_handle, nullptr); + m_handle = nullptr; + } + } + + const std::string& get_source() const + { + return m_source; + } + + const std::vector get_compiled() const + { + return m_compiled; + } + + VkShaderModule get_handle() const + { + return m_handle; + } + }; + class program { std::vector uniforms; @@ -2473,10 +2554,12 @@ public: program& load_uniforms(::glsl::program_domain domain, const std::vector& inputs); bool has_uniform(std::string uniform_name); - void bind_uniform(VkDescriptorImageInfo image_descriptor, std::string uniform_name, VkDescriptorSet &descriptor_set); - void bind_uniform(VkDescriptorBufferInfo buffer_descriptor, uint32_t binding_point, VkDescriptorSet &descriptor_set); + void bind_uniform(const VkDescriptorImageInfo &image_descriptor, std::string uniform_name, VkDescriptorSet &descriptor_set); + void bind_uniform(const VkDescriptorBufferInfo &buffer_descriptor, uint32_t binding_point, VkDescriptorSet &descriptor_set); void bind_uniform(const VkBufferView &buffer_view, const std::string &binding_name, VkDescriptorSet &descriptor_set); + void bind_buffer(const VkDescriptorBufferInfo &buffer_descriptor, uint32_t binding_point, VkDescriptorType type, VkDescriptorSet &descriptor_set); + u64 get_vertex_input_attributes_mask(); }; } diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index 3c05fa3501..9081517fb3 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -11,8 +11,8 @@ namespace vk //TODO: Refactor text print class to inherit from this base class struct overlay_pass { - VKVertexProgram m_vertex_shader; - VKFragmentProgram m_fragment_shader; + vk::glsl::shader m_vertex_shader; + vk::glsl::shader m_fragment_shader; vk::descriptor_pool m_descriptor_pool; VkDescriptorSet m_descriptor_set = nullptr; @@ -149,11 +149,11 @@ namespace vk { if (!compiled) { - m_vertex_shader.shader = vs_src; - m_vertex_shader.Compile(); + m_vertex_shader.create(::glsl::program_domain::glsl_vertex_program, vs_src); + m_vertex_shader.compile(); - m_fragment_shader.shader = fs_src; - m_fragment_shader.Compile(); + m_fragment_shader.create(::glsl::program_domain::glsl_fragment_program, fs_src); + m_fragment_shader.compile(); compiled = true; } @@ -161,12 +161,12 @@ namespace vk VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shader_stages[0].module = m_vertex_shader.handle; + shader_stages[0].module = m_vertex_shader.get_handle(); shader_stages[0].pName = "main"; shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shader_stages[1].module = m_fragment_shader.handle; + shader_stages[1].module = m_fragment_shader.get_handle(); shader_stages[1].pName = "main"; VkDynamicState dynamic_state_descriptors[VK_DYNAMIC_STATE_RANGE_SIZE] = {}; @@ -282,6 +282,8 @@ namespace vk { if (initialized) { + m_vertex_shader.destroy(); + m_fragment_shader.destroy(); m_program_cache.clear(); m_sampler.reset(); @@ -434,9 +436,6 @@ namespace vk renderpass_config.set_depth_mask(true); renderpass_config.enable_depth_test(VK_COMPARE_OP_ALWAYS); renderpass_config.enable_stencil_test(VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE, VK_COMPARE_OP_ALWAYS, 0xFF, 0xFF); - - m_vertex_shader.id = 100002; - m_fragment_shader.id = 100003; } }; @@ -521,9 +520,6 @@ namespace vk VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, VK_BLEND_OP_ADD); - - m_vertex_shader.id = 100004; - m_fragment_shader.id = 100005; } vk::image_view* upload_simple_texture(vk::render_device &dev, vk::command_buffer &cmd, @@ -784,9 +780,6 @@ namespace vk renderpass_config.set_depth_mask(false); renderpass_config.set_color_mask(true, true, true, true); renderpass_config.set_attachment_count(1); - - m_vertex_shader.id = 100006; - m_fragment_shader.id = 100007; } void update_uniforms(vk::glsl::program* /*program*/) override diff --git a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp index b27786847a..a93bab8ce6 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp +++ b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp @@ -48,7 +48,7 @@ namespace vk return false; } - void program::bind_uniform(VkDescriptorImageInfo image_descriptor, std::string uniform_name, VkDescriptorSet &descriptor_set) + void program::bind_uniform(const VkDescriptorImageInfo &image_descriptor, std::string uniform_name, VkDescriptorSet &descriptor_set) { for (auto &uniform : uniforms) { @@ -72,19 +72,9 @@ namespace vk LOG_NOTICE(RSX, "texture not found in program: %s", uniform_name.c_str()); } - void program::bind_uniform(VkDescriptorBufferInfo buffer_descriptor, uint32_t binding_point, VkDescriptorSet &descriptor_set) + void program::bind_uniform(const VkDescriptorBufferInfo &buffer_descriptor, uint32_t binding_point, VkDescriptorSet &descriptor_set) { - VkWriteDescriptorSet descriptor_writer = {}; - descriptor_writer.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptor_writer.dstSet = descriptor_set; - descriptor_writer.descriptorCount = 1; - descriptor_writer.pBufferInfo = &buffer_descriptor; - descriptor_writer.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptor_writer.dstArrayElement = 0; - descriptor_writer.dstBinding = binding_point; - - vkUpdateDescriptorSets(m_device, 1, &descriptor_writer, 0, nullptr); - attribute_location_mask |= (1ull << binding_point); + bind_buffer(buffer_descriptor, binding_point, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptor_set); } void program::bind_uniform(const VkBufferView &buffer_view, const std::string &binding_name, VkDescriptorSet &descriptor_set) @@ -111,6 +101,21 @@ namespace vk LOG_NOTICE(RSX, "vertex buffer not found in program: %s", binding_name.c_str()); } + void program::bind_buffer(const VkDescriptorBufferInfo &buffer_descriptor, uint32_t binding_point, VkDescriptorType type, VkDescriptorSet &descriptor_set) + { + VkWriteDescriptorSet descriptor_writer = {}; + descriptor_writer.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptor_writer.dstSet = descriptor_set; + descriptor_writer.descriptorCount = 1; + descriptor_writer.pBufferInfo = &buffer_descriptor; + descriptor_writer.descriptorType = type; + descriptor_writer.dstArrayElement = 0; + descriptor_writer.dstBinding = binding_point; + + vkUpdateDescriptorSets(m_device, 1, &descriptor_writer, 0, nullptr); + attribute_location_mask |= (1ull << binding_point); + } + u64 program::get_vertex_input_attributes_mask() { if (vertex_attributes_mask) diff --git a/rpcs3/Emu/RSX/VK/VKTextOut.h b/rpcs3/Emu/RSX/VK/VKTextOut.h index d40a8ba072..020037dd62 100644 --- a/rpcs3/Emu/RSX/VK/VKTextOut.h +++ b/rpcs3/Emu/RSX/VK/VKTextOut.h @@ -13,8 +13,8 @@ namespace vk std::unique_ptr m_uniforms_buffer; std::unique_ptr m_program; - VKVertexProgram m_vertex_shader; - VKFragmentProgram m_fragment_shader; + vk::glsl::shader m_vertex_shader; + vk::glsl::shader m_fragment_shader; vk::descriptor_pool m_descriptor_pool; VkDescriptorSet m_descriptor_set = nullptr; @@ -102,23 +102,21 @@ namespace vk "}\n" }; - m_vertex_shader.shader = vs; - m_vertex_shader.id = 100000; - m_vertex_shader.Compile(); + m_vertex_shader.create(::glsl::program_domain::glsl_vertex_program, vs); + m_vertex_shader.compile(); - m_fragment_shader.shader = fs; - m_fragment_shader.id = 100001; - m_fragment_shader.Compile(); + m_fragment_shader.create(::glsl::program_domain::glsl_fragment_program, fs); + m_fragment_shader.compile(); VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shader_stages[0].module = m_vertex_shader.handle; + shader_stages[0].module = m_vertex_shader.get_handle(); shader_stages[0].pName = "main"; shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shader_stages[1].module = m_fragment_shader.handle; + shader_stages[1].module = m_fragment_shader.get_handle(); shader_stages[1].pName = "main"; VkDynamicState dynamic_state_descriptors[VK_DYNAMIC_STATE_RANGE_SIZE] = {}; @@ -246,6 +244,9 @@ namespace vk { if (initialized) { + m_vertex_shader.destroy(); + m_fragment_shader.destroy(); + vkDestroyDescriptorSetLayout(device, m_descriptor_layout, nullptr); vkDestroyPipelineLayout(device, m_pipeline_layout, nullptr); m_descriptor_pool.destroy(); diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index 7d2e79224e..3a5a5bb77a 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -5,6 +5,7 @@ #include "../RSXTexture.h" #include "../rsx_utils.h" #include "VKFormats.h" +#include "VKCompute.h" namespace vk { @@ -55,24 +56,75 @@ namespace vk } } - void copy_image_typeless(VkCommandBuffer cmd, VkImage &src, VkImage &dst, VkImageLayout srcLayout, VkImageLayout dstLayout, - const areai& src_rect, const areai& dst_rect, u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, - VkImageAspectFlags src_transfer_mask, VkImageAspectFlags dst_transfer_mask) + std::pair get_format_convert_flags(VkFormat format) { - if (src == dst) + switch (format) { - copy_image(cmd, src, dst, srcLayout, dstLayout, src_rect, dst_rect, mipmaps, src_aspect, dst_aspect, src_transfer_mask, dst_transfer_mask); + //8-bit + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R8G8_SNORM: + case VK_FORMAT_A8B8G8R8_UNORM_PACK32: + case VK_FORMAT_R8G8B8A8_UNORM: + return{ false, 1 }; + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + return{ true, 4 }; + //16-bit + case VK_FORMAT_R16_UINT: + case VK_FORMAT_R16_SFLOAT: + case VK_FORMAT_R16_UNORM: + case VK_FORMAT_R16G16_UNORM: + case VK_FORMAT_R16G16_SFLOAT: + case VK_FORMAT_R16G16B16A16_SFLOAT: + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: + return{ true, 2 }; + //32-bit + case VK_FORMAT_R32_UINT: + case VK_FORMAT_R32_SFLOAT: + case VK_FORMAT_R32G32B32A32_SFLOAT: + return{ true, 4 }; + //DXT + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: + case VK_FORMAT_BC2_UNORM_BLOCK: + case VK_FORMAT_BC3_UNORM_BLOCK: + case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: + case VK_FORMAT_BC2_SRGB_BLOCK: + case VK_FORMAT_BC3_SRGB_BLOCK: + return{ false, 1 }; + //Depth + case VK_FORMAT_D16_UNORM: + return{ true, 2 }; + case VK_FORMAT_D32_SFLOAT_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + return{ true, 4 }; + } + + fmt::throw_exception("Unknown vkFormat 0x%x" HERE, (u32)format); + } + + void copy_image_typeless(const vk::command_buffer& cmd, const vk::image* src, const vk::image* dst, const areai& src_rect, const areai& dst_rect, + u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, VkImageAspectFlags src_transfer_mask, VkImageAspectFlags dst_transfer_mask) + { + if (src->info.format == dst->info.format) + { + copy_image(cmd, src->value, dst->value, src->current_layout, dst->current_layout, src_rect, dst_rect, mipmaps, src_aspect, dst_aspect, src_transfer_mask, dst_transfer_mask); return; } auto preferred_src_format = (src == dst) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; auto preferred_dst_format = (src == dst) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + const auto src_layout = src->current_layout; + const auto dst_layout = dst->current_layout; - if (srcLayout != preferred_src_format) - change_image_layout(cmd, src, srcLayout, preferred_src_format, vk::get_image_subresource_range(0, 0, 1, 1, src_aspect)); + if (src->current_layout != preferred_src_format) + change_image_layout(cmd, src->value, src_layout, preferred_src_format, vk::get_image_subresource_range(0, 0, 1, 1, src_aspect)); - if (dstLayout != preferred_dst_format) - change_image_layout(cmd, dst, dstLayout, preferred_dst_format, vk::get_image_subresource_range(0, 0, 1, 1, dst_aspect)); + if (dst->current_layout != preferred_dst_format) + change_image_layout(cmd, dst->value, dst_layout, preferred_dst_format, vk::get_image_subresource_range(0, 0, 1, 1, dst_aspect)); auto scratch_buf = vk::get_scratch_buffer(); VkBufferImageCopy src_copy{}, dst_copy{}; @@ -86,21 +138,69 @@ namespace vk for (u32 mip_level = 0; mip_level < mipmaps; ++mip_level) { - vkCmdCopyImageToBuffer(cmd, src, preferred_src_format, scratch_buf->value, 1, &src_copy); - vkCmdCopyBufferToImage(cmd, scratch_buf->value, dst, preferred_dst_format, 1, &dst_copy); + vkCmdCopyImageToBuffer(cmd, src->value, preferred_src_format, scratch_buf->value, 1, &src_copy); + + const auto src_convert = get_format_convert_flags(src->info.format); + const auto dst_convert = get_format_convert_flags(dst->info.format); + + if (src_convert.first || dst_convert.first) + { + if (src_convert.first == dst_convert.first && + src_convert.second == dst_convert.second) + { + // NOP, the two operations will cancel out + } + else + { + insert_buffer_memory_barrier(cmd, scratch_buf->value, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + + vk::cs_shuffle_base *shuffle_kernel = nullptr; + if (src_convert.first && dst_convert.first) + { + shuffle_kernel = vk::get_compute_task(); + } + else + { + const auto block_size = src_convert.first ? src_convert.second : dst_convert.second; + if (block_size == 4) + { + shuffle_kernel = vk::get_compute_task(); + } + else if (block_size == 2) + { + shuffle_kernel = vk::get_compute_task(); + } + else + { + fmt::throw_exception("Unreachable" HERE); + } + } + + const auto elem_size = vk::get_format_texel_width(src->info.format); + const auto length = elem_size * src_copy.imageExtent.width * src_copy.imageExtent.height; + + shuffle_kernel->run(cmd, scratch_buf, length); + + insert_buffer_memory_barrier(cmd, scratch_buf->value, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + } + } + + vkCmdCopyBufferToImage(cmd, scratch_buf->value, dst->value, preferred_dst_format, 1, &dst_copy); src_copy.imageSubresource.mipLevel++; dst_copy.imageSubresource.mipLevel++; } - if (srcLayout != preferred_src_format) - change_image_layout(cmd, src, preferred_src_format, srcLayout, vk::get_image_subresource_range(0, 0, 1, 1, src_aspect)); + if (src_layout != preferred_src_format) + change_image_layout(cmd, src->value, preferred_src_format, src_layout, vk::get_image_subresource_range(0, 0, 1, 1, src_aspect)); - if (dstLayout != preferred_dst_format) - change_image_layout(cmd, dst, preferred_dst_format, dstLayout, vk::get_image_subresource_range(0, 0, 1, 1, dst_aspect)); + if (dst_layout != preferred_dst_format) + change_image_layout(cmd, dst->value, preferred_dst_format, dst_layout, vk::get_image_subresource_range(0, 0, 1, 1, dst_aspect)); } - void copy_image(VkCommandBuffer cmd, VkImage &src, VkImage &dst, VkImageLayout srcLayout, VkImageLayout dstLayout, + void copy_image(VkCommandBuffer cmd, VkImage src, VkImage dst, VkImageLayout srcLayout, VkImageLayout dstLayout, const areai& src_rect, const areai& dst_rect, u32 mipmaps, VkImageAspectFlags src_aspect, VkImageAspectFlags dst_aspect, VkImageAspectFlags src_transfer_mask, VkImageAspectFlags dst_transfer_mask) { @@ -150,7 +250,7 @@ namespace vk } void copy_scaled_image(VkCommandBuffer cmd, - VkImage & src, VkImage & dst, + VkImage src, VkImage dst, VkImageLayout srcLayout, VkImageLayout dstLayout, u32 src_x_offset, u32 src_y_offset, u32 src_width, u32 src_height, u32 dst_x_offset, u32 dst_y_offset, u32 dst_width, u32 dst_height, diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.h b/rpcs3/Emu/RSX/VK/VKTextureCache.h index 684609dba9..41f6d86f0e 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.h +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.h @@ -1195,8 +1195,7 @@ namespace vk src_area.x1 = (u16)(src_area.x1 * xfer_info.src_scaling_hint); src_area.x2 = (u16)(src_area.x2 * xfer_info.src_scaling_hint); - vk::copy_image_typeless(*commands, src->value, real_src->value, src->current_layout, real_src->current_layout, - { 0, 0, (s32)src->width(), (s32)src->height() }, { 0, 0, (s32)internal_width, (s32)src->height() }, 1, + vk::copy_image_typeless(*commands, src, real_src, { 0, 0, (s32)src->width(), (s32)src->height() }, { 0, 0, (s32)internal_width, (s32)src->height() }, 1, vk::get_aspect_flags(src->info.format), vk::get_aspect_flags(format)); } @@ -1210,8 +1209,7 @@ namespace vk dst_area.x1 = (u16)(dst_area.x1 * xfer_info.dst_scaling_hint); dst_area.x2 = (u16)(dst_area.x2 * xfer_info.dst_scaling_hint); - vk::copy_image_typeless(*commands, dst->value, real_dst->value, dst->current_layout, real_dst->current_layout, - { 0, 0, (s32)dst->width(), (s32)dst->height() }, { 0, 0, (s32)internal_width, (s32)dst->height() }, 1, + vk::copy_image_typeless(*commands, dst, real_dst, { 0, 0, (s32)dst->width(), (s32)dst->height() }, { 0, 0, (s32)internal_width, (s32)dst->height() }, 1, vk::get_aspect_flags(dst->info.format), vk::get_aspect_flags(format)); } @@ -1246,8 +1244,7 @@ namespace vk if (real_dst != dst) { auto internal_width = dst->width() * xfer_info.dst_scaling_hint; - vk::copy_image_typeless(*commands, real_dst->value, dst->value, real_dst->current_layout, dst->current_layout, - { 0, 0, (s32)internal_width, (s32)dst->height() }, { 0, 0, (s32)dst->width(), (s32)dst->height() }, 1, + vk::copy_image_typeless(*commands, real_dst, dst, { 0, 0, (s32)internal_width, (s32)dst->height() }, { 0, 0, (s32)dst->width(), (s32)dst->height() }, 1, vk::get_aspect_flags(real_dst->info.format), vk::get_aspect_flags(dst->info.format)); } diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp index df032ac84a..26b3fef57a 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp @@ -336,41 +336,23 @@ VKVertexProgram::~VKVertexProgram() void VKVertexProgram::Decompile(const RSXVertexProgram& prog) { - VKVertexDecompilerThread decompiler(prog, shader, parr, *this); + std::string source; + VKVertexDecompilerThread decompiler(prog, source, parr, *this); decompiler.Task(); + + shader.create(::glsl::program_domain::glsl_vertex_program, source); } void VKVertexProgram::Compile() { fs::create_path(fs::get_config_dir() + "/shaderlog"); - fs::file(fs::get_config_dir() + "shaderlog/VertexProgram" + std::to_string(id) + ".spirv", fs::rewrite).write(shader); - - std::vector spir_v; - if (!vk::compile_glsl_to_spv(shader, glsl::glsl_vertex_program, spir_v)) - fmt::throw_exception("Failed to compile vertex shader" HERE); - - VkShaderModuleCreateInfo vs_info; - vs_info.codeSize = spir_v.size() * sizeof(u32); - vs_info.pNext = nullptr; - vs_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - vs_info.pCode = (uint32_t*)spir_v.data(); - vs_info.flags = 0; - - VkDevice dev = (VkDevice)*vk::get_current_renderer(); - vkCreateShaderModule(dev, &vs_info, nullptr, &handle); + fs::file(fs::get_config_dir() + "shaderlog/VertexProgram" + std::to_string(id) + ".spirv", fs::rewrite).write(shader.get_source()); + handle = shader.compile(); } void VKVertexProgram::Delete() { - shader.clear(); - - if (handle) - { - VkDevice dev = (VkDevice)*vk::get_current_renderer(); - vkDestroyShaderModule(dev, handle, nullptr); - - handle = nullptr; - } + shader.destroy(); } void VKVertexProgram::SetInputs(std::vector& inputs) diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.h b/rpcs3/Emu/RSX/VK/VKVertexProgram.h index 4e09a3fd85..744c8c3396 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.h +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.h @@ -46,7 +46,7 @@ public: ParamArray parr; VkShaderModule handle = nullptr; u32 id; - std::string shader; + vk::glsl::shader shader; std::vector uniforms; void Decompile(const RSXVertexProgram& prog); diff --git a/rpcs3/VKGSRender.vcxproj b/rpcs3/VKGSRender.vcxproj index f1e72f3bd9..f10ffd9f33 100644 --- a/rpcs3/VKGSRender.vcxproj +++ b/rpcs3/VKGSRender.vcxproj @@ -24,6 +24,7 @@ + diff --git a/rpcs3/VKGSRender.vcxproj.filters b/rpcs3/VKGSRender.vcxproj.filters index fb27ec40ce..7f0265a13a 100644 --- a/rpcs3/VKGSRender.vcxproj.filters +++ b/rpcs3/VKGSRender.vcxproj.filters @@ -43,6 +43,9 @@ Source Files + + Source Files +