diff --git a/rpcs3/Emu/RSX/CgBinaryProgram.h b/rpcs3/Emu/RSX/CgBinaryProgram.h index 62ebe81837..921bf96e3c 100644 --- a/rpcs3/Emu/RSX/CgBinaryProgram.h +++ b/rpcs3/Emu/RSX/CgBinaryProgram.h @@ -335,7 +335,7 @@ public: u32 ctrl = (vmfprog.outputFromH0 ? 0 : 0x40) | (vmfprog.depthReplace ? 0xe : 0); std::vector td; RSXFragmentProgram prog; - prog.size = 0, prog.addr = vm::base(ptr + vmprog.ucode), prog.offset = 0, prog.ctrl = ctrl; + prog.ucode_length = 0, prog.addr = vm::base(ptr + vmprog.ucode), prog.offset = 0, prog.ctrl = ctrl; GLFragmentDecompilerThread(m_glsl_shader, param_array, prog, size).Task(); vm::close(); } diff --git a/rpcs3/Emu/RSX/Common/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Common/ProgramStateCache.cpp index 61221b2ad8..982d6cf141 100644 --- a/rpcs3/Emu/RSX/Common/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Common/ProgramStateCache.cpp @@ -326,20 +326,20 @@ size_t fragment_program_utils::get_fragment_program_ucode_size(void *ptr) fragment_program_utils::fragment_program_metadata fragment_program_utils::analyse_fragment_program(void *ptr) { const qword *instBuffer = (const qword*)ptr; - size_t instIndex = 0; + s32 index = 0; s32 program_offset = -1; u32 ucode_size = 0; u16 textures_mask = 0; while (true) { - const qword& inst = instBuffer[instIndex]; + const qword& inst = instBuffer[index]; const u32 opcode = (inst.word[0] >> 16) & 0x3F; if (opcode) { if (program_offset < 0) - program_offset = instIndex * 16; + program_offset = index * 16; switch(opcode) { @@ -362,7 +362,7 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys if (is_constant(inst.word[1]) || is_constant(inst.word[2]) || is_constant(inst.word[3])) { //Instruction references constant, skip one slot occupied by data - instIndex++; + index++; ucode_size += 16; } } @@ -376,14 +376,14 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys { if (program_offset < 0) { - program_offset = instIndex * 16; + program_offset = index * 16; ucode_size = 16; } break; } - instIndex++; + index++; } return{ (u32)program_offset, ucode_size, textures_mask }; diff --git a/rpcs3/Emu/RSX/Common/ProgramStateCache.h b/rpcs3/Emu/RSX/Common/ProgramStateCache.h index 210ae351e7..97318010d2 100644 --- a/rpcs3/Emu/RSX/Common/ProgramStateCache.h +++ b/rpcs3/Emu/RSX/Common/ProgramStateCache.h @@ -6,7 +6,9 @@ #include "Utilities/GSL.h" #include "Utilities/hash.h" -#include +#include "Utilities/mutex.h" + +#include enum class SHADER_TYPE { @@ -136,22 +138,73 @@ class program_state_cache } }; +public: + struct async_link_task_entry + { + const vertex_program_type& vp; + const fragment_program_type& fp; + pipeline_properties props; + + async_link_task_entry(const vertex_program_type& _V, const fragment_program_type& _F, const pipeline_properties& _P) + : vp(_V), fp(_F), props(_P) + {} + }; + + struct async_decompile_task_entry + { + RSXVertexProgram vp; + RSXFragmentProgram fp; + bool is_fp; + + std::vector tmp_cache; + + async_decompile_task_entry(const RSXVertexProgram& _V) + : vp(_V), is_fp(false) + { + } + + async_decompile_task_entry(const RSXFragmentProgram& _F) + : fp(_F), is_fp(true) + { + tmp_cache.resize(fp.ucode_length); + std::memcpy(tmp_cache.data(), fp.addr, fp.ucode_length); + fp.addr = tmp_cache.data(); + } + }; + protected: - std::mutex s_mtx; // TODO: Only need to synchronize when loading cache + shared_mutex m_pipeline_mutex; + shared_mutex m_decompiler_mutex; + size_t m_next_id = 0; - bool m_cache_miss_flag; + bool m_cache_miss_flag; // Set if last lookup did not find any usable cached programs + bool m_program_compiled_flag; // Set if last lookup caused program to be linked + binary_to_vertex_program m_vertex_shader_cache; binary_to_fragment_program m_fragment_shader_cache; std::unordered_map m_storage; + std::unordered_map , pipeline_key_hash, pipeline_key_compare> m_link_queue; + std::deque m_decompile_queue; + + vertex_program_type __null_vertex_program; + fragment_program_type __null_fragment_program; + pipeline_storage_type __null_pipeline_handle; + /// bool here to inform that the program was preexisting. - std::tuple search_vertex_program(const RSXVertexProgram& rsx_vp) + std::tuple search_vertex_program(const RSXVertexProgram& rsx_vp, bool force_load = true) { const auto& I = m_vertex_shader_cache.find(rsx_vp); if (I != m_vertex_shader_cache.end()) { return std::forward_as_tuple(I->second, true); } + + if (!force_load) + { + return std::forward_as_tuple(__null_vertex_program, false); + } + LOG_NOTICE(RSX, "VP not found in buffer!"); vertex_program_type& new_shader = m_vertex_shader_cache[rsx_vp]; backend_traits::recompile_vertex_program(rsx_vp, new_shader, m_next_id++); @@ -160,17 +213,22 @@ protected: } /// bool here to inform that the program was preexisting. - std::tuple search_fragment_program(const RSXFragmentProgram& rsx_fp) + std::tuple search_fragment_program(const RSXFragmentProgram& rsx_fp, bool force_load = true) { const auto& I = m_fragment_shader_cache.find(rsx_fp); if (I != m_fragment_shader_cache.end()) { return std::forward_as_tuple(I->second, true); } + + if (!force_load) + { + return std::forward_as_tuple(__null_fragment_program, false); + } + LOG_NOTICE(RSX, "FP not found in buffer!"); - size_t fragment_program_size = program_hash_util::fragment_program_utils::get_fragment_program_ucode_size(rsx_fp.addr); - gsl::not_null fragment_program_ucode_copy = malloc(fragment_program_size); - std::memcpy(fragment_program_ucode_copy, rsx_fp.addr, fragment_program_size); + gsl::not_null fragment_program_ucode_copy = malloc(rsx_fp.ucode_length); + std::memcpy(fragment_program_ucode_copy, rsx_fp.addr, rsx_fp.ucode_length); RSXFragmentProgram new_fp_key = rsx_fp; new_fp_key.addr = fragment_program_ucode_copy; fragment_program_type &new_shader = m_fragment_shader_cache[new_fp_key]; @@ -277,45 +335,173 @@ public: } template - pipeline_storage_type& getGraphicPipelineState( + bool async_update(u32 max_decompile_count, Args&& ...args) + { + // Decompile shaders and link one pipeline object per 'run' + // NOTE: Linking is much slower than decompilation step, so always decompile at least 1 unit + // TODO: Use try_lock instead + bool busy = false; + { + u32 count = 0; + writer_lock lock(m_decompiler_mutex); + + while (!m_decompile_queue.empty()) + { + const auto& decompile_task = m_decompile_queue.front(); + if (decompile_task.is_fp) + { + search_fragment_program(decompile_task.fp); + } + else + { + search_vertex_program(decompile_task.vp); + } + + m_decompile_queue.pop_front(); + + if (++count >= max_decompile_count) + { + // Allows configurable decompiler 'load' + // Smaller unit count will release locks faster + busy = true; + break; + } + } + } + + async_link_task_entry* link_entry; + pipeline_key key; + { + reader_lock lock(m_pipeline_mutex); + if (!m_link_queue.empty()) + { + auto It = m_link_queue.begin(); + link_entry = It->second.get(); + key = It->first; + } + else + { + return busy; + } + } + + pipeline_storage_type pipeline = backend_traits::build_pipeline(link_entry->vp, link_entry->fp, link_entry->props, std::forward(args)...); + LOG_SUCCESS(RSX, "New program compiled successfully"); + + writer_lock lock(m_pipeline_mutex); + m_storage[key] = std::move(pipeline); + m_link_queue.erase(key); + + return (busy || !m_link_queue.empty()); + } + + template + pipeline_storage_type& get_graphics_pipeline( const RSXVertexProgram& vertexShader, const RSXFragmentProgram& fragmentShader, pipeline_properties& pipelineProperties, + bool allow_async, Args&& ...args ) { - // TODO : use tie and implicit variable declaration syntax with c++17 - const auto &vp_search = search_vertex_program(vertexShader); - const auto &fp_search = search_fragment_program(fragmentShader); - const vertex_program_type &vertex_program = std::get<0>(vp_search); - const fragment_program_type &fragment_program = std::get<0>(fp_search); - bool already_existing_fragment_program = std::get<1>(fp_search); - bool already_existing_vertex_program = std::get<1>(vp_search); + const auto &vp_search = search_vertex_program(vertexShader, !allow_async); + const auto &fp_search = search_fragment_program(fragmentShader, !allow_async); - backend_traits::validate_pipeline_properties(vertex_program, fragment_program, pipelineProperties); - pipeline_key key = { vertex_program.id, fragment_program.id, pipelineProperties }; + const bool already_existing_fragment_program = std::get<1>(fp_search); + const bool already_existing_vertex_program = std::get<1>(vp_search); - if (already_existing_fragment_program && already_existing_vertex_program) + bool link_only = false; + m_cache_miss_flag = true; + m_program_compiled_flag = false; + + if (!allow_async || (already_existing_vertex_program && already_existing_fragment_program)) { + const vertex_program_type &vertex_program = std::get<0>(vp_search); + const fragment_program_type &fragment_program = std::get<0>(fp_search); + + backend_traits::validate_pipeline_properties(vertex_program, fragment_program, pipelineProperties); + pipeline_key key = { vertex_program.id, fragment_program.id, pipelineProperties }; + const auto I = m_storage.find(key); if (I != m_storage.end()) { m_cache_miss_flag = false; return I->second; } + + if (allow_async) + { + // Programs already exist, only linking required + link_only = true; + } + else + { + LOG_NOTICE(RSX, "Add program (vp id = %d, fp id = %d)", vertex_program.id, fragment_program.id); + m_program_compiled_flag = true; + + pipeline_storage_type pipeline = backend_traits::build_pipeline(vertex_program, fragment_program, pipelineProperties, std::forward(args)...); + writer_lock lock(m_pipeline_mutex); + auto &rtn = m_storage[key] = std::move(pipeline); + LOG_SUCCESS(RSX, "New program compiled successfully"); + return rtn; + } } - LOG_NOTICE(RSX, "Add program :"); - LOG_NOTICE(RSX, "*** vp id = %d", vertex_program.id); - LOG_NOTICE(RSX, "*** fp id = %d", fragment_program.id); + verify(HERE), allow_async; - pipeline_storage_type pipeline = backend_traits::build_pipeline(vertex_program, fragment_program, pipelineProperties, std::forward(args)...); - std::lock_guard lock(s_mtx); - auto &rtn = m_storage[key] = std::move(pipeline); - m_cache_miss_flag = true; + if (link_only) + { + const vertex_program_type &vertex_program = std::get<0>(vp_search); + const fragment_program_type &fragment_program = std::get<0>(fp_search); + pipeline_key key = { vertex_program.id, fragment_program.id, pipelineProperties }; - LOG_SUCCESS(RSX, "New program compiled successfully"); - return rtn; + reader_lock lock(m_pipeline_mutex); + + if (m_link_queue.find(key) != m_link_queue.end()) + { + // Already in queue + return __null_pipeline_handle; + } + + LOG_NOTICE(RSX, "Add program (vp id = %d, fp id = %d)", vertex_program.id, fragment_program.id); + m_program_compiled_flag = true; + + lock.upgrade(); + m_link_queue[key] = std::make_unique(vertex_program, fragment_program, pipelineProperties); + } + else + { + reader_lock lock(m_decompiler_mutex); + + auto vertex_program_found = std::find_if(m_decompile_queue.begin(), m_decompile_queue.end(), [&](const auto& V) + { + if (V.is_fp) return false; + return program_hash_util::vertex_program_compare()(V.vp, vertexShader); + }); + + auto fragment_program_found = std::find_if(m_decompile_queue.begin(), m_decompile_queue.end(), [&](const auto& F) + { + if (!F.is_fp) return false; + return program_hash_util::fragment_program_compare()(F.fp, fragmentShader); + }); + + const bool add_vertex_program = (vertex_program_found == m_decompile_queue.end()); + const bool add_fragment_program = (fragment_program_found == m_decompile_queue.end()); + + if (add_vertex_program) + { + lock.upgrade(); + m_decompile_queue.emplace_back(vertexShader); + } + + if (add_fragment_program) + { + lock.upgrade(); + m_decompile_queue.emplace_back(fragmentShader); + } + } + + return __null_pipeline_handle; } size_t get_fragment_constants_buffer_size(const RSXFragmentProgram &fragmentShader) const diff --git a/rpcs3/Emu/RSX/D3D12/D3D12PipelineState.cpp b/rpcs3/Emu/RSX/D3D12/D3D12PipelineState.cpp index 93c61500b3..80b51607e4 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12PipelineState.cpp +++ b/rpcs3/Emu/RSX/D3D12/D3D12PipelineState.cpp @@ -310,7 +310,7 @@ void D3D12GSRender::load_program() } } - m_current_pso = m_pso_cache.getGraphicPipelineState(current_vertex_program, current_fragment_program, prop, m_device.Get(), m_shared_root_signature.Get()); + m_current_pso = m_pso_cache.get_graphics_pipeline(current_vertex_program, current_fragment_program, prop, false, m_device.Get(), m_shared_root_signature.Get()); return; } diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 970bb63c32..cbf39fe4a7 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -372,9 +372,19 @@ void GLGSRender::end() } std::chrono::time_point program_start = steady_clock::now(); - //Load program here since it is dependent on vertex state - load_program(upload_info); + // NOTE: Due to common OpenGL driver architecture, vertex data has to be uploaded as far away from the draw as possible + // TODO: Implement shaders cache prediction to avoid uploading vertex data if draw is going to skip + if (!load_program()) + { + // Program is not ready, skip drawing this + std::this_thread::yield(); + rsx::thread::end(); + return; + } + + // Load program here since it is dependent on vertex state + load_program_env(upload_info); std::chrono::time_point program_stop = steady_clock::now(); m_begin_time += (u32)std::chrono::duration_cast(program_stop - program_start).count(); @@ -619,7 +629,16 @@ void GLGSRender::set_viewport() void GLGSRender::on_init_thread() { - GSRender::on_init_thread(); + verify(HERE), m_frame; + + // NOTES: All contexts have to be created before any is bound to a thread + // This allows context sharing to work (both GLRCs passed to wglShareLists have to be idle or you get ERROR_BUSY) + m_context = m_frame->make_context(); + m_decompiler_context = m_frame->make_context(); + + // Bind primary context to main RSX thread + m_frame->set_current(m_context); + zcull_ctrl.reset(static_cast<::rsx::reports::ZCULL_control*>(this)); gl::init(); @@ -1108,7 +1127,7 @@ bool GLGSRender::check_program_state() return (rsx::method_registers.shader_program_address() != 0); } -void GLGSRender::load_program(const gl::vertex_upload_info& upload_info) +bool GLGSRender::load_program() { if (m_graphics_state & rsx::pipeline_state::invalidate_pipeline_bits) { @@ -1119,35 +1138,49 @@ void GLGSRender::load_program(const gl::vertex_upload_info& upload_info) current_vertex_program.skip_vertex_input_check = true; //not needed for us since decoding is done server side current_fragment_program.unnormalized_coords = 0; //unused - void* pipeline_properties = nullptr; + } + else if (m_program) + { + // Program already loaded + return true; + } - m_program = &m_prog_buffer.getGraphicPipelineState(current_vertex_program, current_fragment_program, pipeline_properties); - m_program->use(); + void* pipeline_properties = nullptr; + m_program = m_prog_buffer.get_graphics_pipeline(current_vertex_program, current_fragment_program, pipeline_properties, + !g_cfg.video.disable_asynchronous_shader_compiler).get(); - if (m_prog_buffer.check_cache_missed()) + if (m_prog_buffer.check_cache_missed()) + { + if (m_prog_buffer.check_program_linked_flag()) { + // Program was linked or queued for linking m_shaders_cache->store(pipeline_properties, current_vertex_program, current_fragment_program); + } - //Notify the user with HUD notification - if (g_cfg.misc.show_shader_compilation_hint) + // Notify the user with HUD notification + if (g_cfg.misc.show_shader_compilation_hint) + { + if (m_overlay_manager) { - if (m_overlay_manager) + if (auto dlg = m_overlay_manager->get()) { - if (auto dlg = m_overlay_manager->get()) - { - //Extend duration - dlg->touch(); - } - else - { - //Create dialog but do not show immediately - m_overlay_manager->create(); - } + // Extend duration + dlg->touch(); + } + else + { + // Create dialog but do not show immediately + m_overlay_manager->create(); } } } } + return m_program != nullptr; +} + +void GLGSRender::load_program_env(const gl::vertex_upload_info& upload_info) +{ u8 *buf; u32 vertex_state_offset; u32 vertex_constants_offset; @@ -1157,6 +1190,13 @@ void GLGSRender::load_program(const gl::vertex_upload_info& upload_info) const u32 fragment_buffer_size = fragment_constants_size + (18 * 4 * sizeof(float)); const bool update_transform_constants = !!(m_graphics_state & rsx::pipeline_state::transform_constants_dirty); + if (!m_program) + { + fmt::throw_exception("Unreachable right now" HERE); + } + + m_program->use(); + if (manually_flush_ring_buffers) { m_vertex_state_buffer->reserve_storage_on_heap(512); @@ -1212,7 +1252,8 @@ void GLGSRender::load_program(const gl::vertex_upload_info& upload_info) if (update_transform_constants) m_transform_constants_buffer->unmap(); } - m_graphics_state &= ~rsx::pipeline_state::memory_barrier_bits; + const u32 handled_flags = (rsx::pipeline_state::fragment_state_dirty | rsx::pipeline_state::vertex_state_dirty | rsx::pipeline_state::transform_constants_dirty); + m_graphics_state &= ~handled_flags; } void GLGSRender::update_draw_state() @@ -1730,3 +1771,26 @@ void GLGSRender::discard_occlusion_query(rsx::reports::occlusion_query_info* que glEndQuery(GL_ANY_SAMPLES_PASSED); } } + +void GLGSRender::on_decompiler_init() +{ + // Bind decompiler context to this thread + m_frame->set_current(m_decompiler_context); +} + +void GLGSRender::on_decompiler_exit() +{ + // Cleanup + m_frame->delete_context(m_decompiler_context); +} + +bool GLGSRender::on_decompiler_task() +{ + bool ret = m_prog_buffer.async_update(8); + + // TODO: Proper synchronization with renderer + // Finish works well enough for now but it is not a proper soulution + glFinish(); + + return ret; +} diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 491bd27946..c8563adfba 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -329,6 +329,7 @@ private: std::thread::id m_thread_id; GLProgramBuffer m_prog_buffer; + draw_context_t m_decompiler_context; //buffer gl::fbo draw_fbo; @@ -361,7 +362,8 @@ private: void init_buffers(rsx::framebuffer_creation_context context, bool skip_reading = false); bool check_program_state(); - void load_program(const gl::vertex_upload_info& upload_info); + bool load_program(); + void load_program_env(const gl::vertex_upload_info& upload_info); void update_draw_state(); @@ -398,4 +400,8 @@ protected: std::array, 4> copy_render_targets_to_memory() override; std::array, 2> copy_depth_stencil_buffer_to_memory() override; + + void on_decompiler_init() override; + void on_decompiler_exit() override; + bool on_decompiler_task() override; }; diff --git a/rpcs3/Emu/RSX/GL/GLProgramBuffer.h b/rpcs3/Emu/RSX/GL/GLProgramBuffer.h index c30ac209e8..a5d9eb8962 100644 --- a/rpcs3/Emu/RSX/GL/GLProgramBuffer.h +++ b/rpcs3/Emu/RSX/GL/GLProgramBuffer.h @@ -7,7 +7,7 @@ struct GLTraits { using vertex_program_type = GLVertexProgram; using fragment_program_type = GLFragmentProgram; - using pipeline_storage_type = gl::glsl::program; + using pipeline_storage_type = std::unique_ptr; using pipeline_properties = void*; static @@ -32,8 +32,8 @@ struct GLTraits static pipeline_storage_type build_pipeline(const vertex_program_type &vertexProgramData, const fragment_program_type &fragmentProgramData, const pipeline_properties&) { - pipeline_storage_type result; - __glcheck result.create() + pipeline_storage_type result = std::make_unique(); + result->create() .attach(gl::glsl::shader_view(vertexProgramData.id)) .attach(gl::glsl::shader_view(fragmentProgramData.id)) .bind_fragment_data_location("ocol0", 0) @@ -41,31 +41,30 @@ struct GLTraits .bind_fragment_data_location("ocol2", 2) .bind_fragment_data_location("ocol3", 3) .make(); - __glcheck result.use(); //Progam locations are guaranteed to not change after linking //Texture locations are simply bound to the TIUs so this can be done once for (int i = 0; i < rsx::limits::fragment_textures_count; ++i) { int location; - if (result.uniforms.has_location(rsx::constants::fragment_texture_names[i], &location)) - result.uniforms[location] = i; + if (result->uniforms.has_location(rsx::constants::fragment_texture_names[i], &location)) + result->uniforms[location] = i; } for (int i = 0; i < rsx::limits::vertex_textures_count; ++i) { int location; - if (result.uniforms.has_location(rsx::constants::vertex_texture_names[i], &location)) - result.uniforms[location] = (i + rsx::limits::fragment_textures_count); + if (result->uniforms.has_location(rsx::constants::vertex_texture_names[i], &location)) + result->uniforms[location] = (i + rsx::limits::fragment_textures_count); } const int stream_buffer_start = rsx::limits::fragment_textures_count + rsx::limits::vertex_textures_count; //Bind locations 0 and 1 to the stream buffers - result.uniforms[0] = stream_buffer_start; - result.uniforms[1] = stream_buffer_start + 1; + result->uniforms[0] = stream_buffer_start; + result->uniforms[1] = stream_buffer_start + 1; - LOG_NOTICE(RSX, "*** prog id = %d", result.id()); + LOG_NOTICE(RSX, "*** prog id = %d", result->id()); LOG_NOTICE(RSX, "*** vp id = %d", vertexProgramData.id); LOG_NOTICE(RSX, "*** fp id = %d", fragmentProgramData.id); @@ -99,7 +98,7 @@ public: void add_pipeline_entry(RSXVertexProgram &vp, RSXFragmentProgram &fp, void* &props, Args&& ...args) { vp.skip_vertex_input_check = true; - getGraphicPipelineState(vp, fp, props, std::forward(args)...); + get_graphics_pipeline(vp, fp, props, false, std::forward(args)...); } void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp) @@ -112,4 +111,9 @@ public: { return m_cache_miss_flag; } + + bool check_program_linked_flag() const + { + return m_program_compiled_flag; + } }; diff --git a/rpcs3/Emu/RSX/Overlays/overlays.cpp b/rpcs3/Emu/RSX/Overlays/overlays.cpp index d4f1b2ff41..9c9e86012e 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlays.cpp @@ -32,7 +32,11 @@ namespace rsx { if (auto rsxthr = rsx::get_current_renderer()) { - rsxthr->native_ui_flip_request.store(true); + const auto now = get_system_time() - 1000000; + if ((now - rsxthr->last_flip_time) > min_refresh_duration_us) + { + rsxthr->native_ui_flip_request.store(true); + } } } } // namespace overlays diff --git a/rpcs3/Emu/RSX/Overlays/overlays.h b/rpcs3/Emu/RSX/Overlays/overlays.h index a8903f1d9f..dd1895847c 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.h +++ b/rpcs3/Emu/RSX/Overlays/overlays.h @@ -32,6 +32,8 @@ namespace rsx u16 virtual_width = 1280; u16 virtual_height = 720; + u32 min_refresh_duration_us = 16600; + virtual ~overlay() = default; virtual void update() {} @@ -1103,6 +1105,9 @@ namespace rsx creation_time = get_system_time(); expire_time = creation_time + 1000000; + + // Disable forced refresh unless fps dips below 4 + min_refresh_duration_us = 250000; } void update_animation(u64 t) diff --git a/rpcs3/Emu/RSX/RSXFragmentProgram.h b/rpcs3/Emu/RSX/RSXFragmentProgram.h index d71ab1c6e2..10dae66e21 100644 --- a/rpcs3/Emu/RSX/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/RSXFragmentProgram.h @@ -216,9 +216,9 @@ static const std::string rsx_fp_op_names[] = struct RSXFragmentProgram { - u32 size; void *addr; u32 offset; + u32 ucode_length; u32 ctrl; u16 unnormalized_coords; u16 redirected_textures; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 32d6bff805..b8c71f737f 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -422,6 +422,47 @@ namespace rsx } }); + thread_ctrl::spawn(m_decompiler_thread, "RSX Decompiler Thread", [this] + { + if (g_cfg.video.disable_asynchronous_shader_compiler) + { + // Die + return; + } + + on_decompiler_init(); + + if (g_cfg.core.thread_scheduler_enabled) + { + thread_ctrl::set_thread_affinity_mask(thread_ctrl::get_affinity_mask(thread_class::rsx)); + } + + // Weak cpus need all the help they can get, sleep instead of yield loop + // Lowers decompiler responsiveness but improves emulator performance + const bool prefer_sleep = (std::thread::hardware_concurrency() < 6); + + while (!Emu.IsStopped() && !m_rsx_thread_exiting) + { + if (!on_decompiler_task()) + { + if (Emu.IsPaused()) + { + std::this_thread::sleep_for(1ms); + } + else if (prefer_sleep) + { + std::this_thread::sleep_for(500us); + } + else + { + std::this_thread::yield(); + } + } + } + + on_decompiler_exit(); + }); + // Raise priority above other threads thread_ctrl::set_native_priority(1); @@ -969,6 +1010,12 @@ namespace rsx m_vblank_thread->join(); m_vblank_thread.reset(); } + + if (m_decompiler_thread) + { + m_decompiler_thread->join(); + m_decompiler_thread.reset(); + } } std::string thread::get_name() const @@ -1651,6 +1698,7 @@ namespace rsx result.addr = ((u8*)result.addr + current_fp_metadata.program_start_offset); result.offset = program_offset + current_fp_metadata.program_start_offset; + result.ucode_length = current_fp_metadata.program_ucode_length; result.valid = true; result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT); result.unnormalized_coords = 0; @@ -1781,6 +1829,7 @@ namespace rsx result.addr = ((u8*)result.addr + program_info.program_start_offset); result.offset = program_offset + program_info.program_start_offset; + result.ucode_length = program_info.program_ucode_length; result.valid = true; result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT); result.unnormalized_coords = 0; diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 4fd1d44c7e..aa57e215e6 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -275,6 +275,7 @@ namespace rsx class thread : public named_thread { std::shared_ptr m_vblank_thread; + std::shared_ptr m_decompiler_thread; protected: atomic_t m_rsx_thread_exiting{false}; @@ -424,6 +425,10 @@ namespace rsx */ virtual void do_local_task(FIFO_state state); + virtual void on_decompiler_init() {} + virtual void on_decompiler_exit() {} + virtual bool on_decompiler_task() { return false; } + public: virtual std::string get_name() const override; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index bcd8a883f5..2be6fb8f43 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -1066,13 +1066,7 @@ void VKGSRender::end() return; } - //Programs data is dependent on vertex state - std::chrono::time_point vertex_start = steady_clock::now(); - auto upload_info = upload_vertex_data(); - std::chrono::time_point vertex_end = steady_clock::now(); - m_vertex_upload_time += std::chrono::duration_cast(vertex_end - vertex_start).count(); - - std::chrono::time_point textures_start = vertex_end; + std::chrono::time_point textures_start = steady_clock::now(); auto ds = std::get<1>(m_rtts.m_bound_depth_stencil); //Clear any 'dirty' surfaces - possible is a recycled cache surface is used @@ -1303,19 +1297,37 @@ void VKGSRender::end() std::chrono::time_point textures_end = steady_clock::now(); m_textures_upload_time += (u32)std::chrono::duration_cast(textures_end - textures_start).count(); - //Load program std::chrono::time_point program_start = textures_end; - load_program(upload_info); + if (!load_program()) + { + // Program is not ready, skip drawing this + std::this_thread::yield(); + rsx::thread::end(); + return; + } + + std::chrono::time_point program_end = steady_clock::now(); + m_setup_time += std::chrono::duration_cast(program_end - program_start).count(); + + // Programs data is dependent on vertex state + std::chrono::time_point vertex_start = program_end; + auto upload_info = upload_vertex_data(); + std::chrono::time_point vertex_end = steady_clock::now(); + m_vertex_upload_time += std::chrono::duration_cast(vertex_end - vertex_start).count(); + + // Load program execution environment + program_start = textures_end; + load_program_env(upload_info); VkBufferView persistent_buffer = m_persistent_attribute_storage ? m_persistent_attribute_storage->value : null_buffer_view->value; VkBufferView volatile_buffer = m_volatile_attribute_storage ? m_volatile_attribute_storage->value : null_buffer_view->value; m_program->bind_uniform(persistent_buffer, "persistent_input_stream", m_current_frame->descriptor_set); m_program->bind_uniform(volatile_buffer, "volatile_input_stream", m_current_frame->descriptor_set); - std::chrono::time_point program_stop = steady_clock::now(); - m_setup_time += std::chrono::duration_cast(program_stop - program_start).count(); + program_end = steady_clock::now(); + m_setup_time += std::chrono::duration_cast(program_end - program_start).count(); - textures_start = program_stop; + textures_start = program_end; for (int i = 0; i < rsx::limits::fragment_textures_count; ++i) { @@ -2193,7 +2205,7 @@ bool VKGSRender::check_program_status() return (rsx::method_registers.shader_program_address() != 0); } -void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info) +bool VKGSRender::load_program() { if (m_graphics_state & rsx::pipeline_state::invalidate_pipeline_bits) { @@ -2201,13 +2213,15 @@ void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info) verify(HERE), current_fragment_program.valid; get_current_vertex_program(vs_sampler_state); + + m_graphics_state &= ~rsx::pipeline_state::invalidate_pipeline_bits; } auto &vertex_program = current_vertex_program; auto &fragment_program = current_fragment_program; auto old_program = m_program; - vk::pipeline_props properties = {}; + vk::pipeline_props properties{}; // Input assembly bool emulated_primitive_type; @@ -2335,36 +2349,51 @@ void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info) //Load current program from buffer vertex_program.skip_vertex_input_check = true; fragment_program.unnormalized_coords = 0; - m_program = m_prog_buffer->getGraphicPipelineState(vertex_program, fragment_program, properties, *m_device, pipeline_layout).get(); + m_program = m_prog_buffer->get_graphics_pipeline(vertex_program, fragment_program, properties, + !g_cfg.video.disable_asynchronous_shader_compiler, *m_device, pipeline_layout).get(); + + vk::leave_uninterruptible(); if (m_prog_buffer->check_cache_missed()) { - m_shaders_cache->store(properties, vertex_program, fragment_program); + if (m_prog_buffer->check_program_linked_flag()) + { + // Program was linked or queued for linking + m_shaders_cache->store(properties, vertex_program, fragment_program); + } - //Notify the user with HUD notification + // Notify the user with HUD notification if (g_cfg.misc.show_shader_compilation_hint) { if (m_overlay_manager) { if (auto dlg = m_overlay_manager->get()) { - //Extend duration + // Extend duration dlg->touch(); } else { - //Create dialog but do not show immediately + // Create dialog but do not show immediately m_overlay_manager->create(); } } } } - vk::leave_uninterruptible(); + return m_program != nullptr; +} + +void VKGSRender::load_program_env(const vk::vertex_upload_info& vertex_info) +{ + if (!m_program) + { + fmt::throw_exception("Unreachable right now" HERE); + } if (1)//m_graphics_state & (rsx::pipeline_state::fragment_state_dirty | rsx::pipeline_state::vertex_state_dirty)) { - const size_t fragment_constants_sz = m_prog_buffer->get_fragment_constants_buffer_size(fragment_program); + const size_t fragment_constants_sz = m_prog_buffer->get_fragment_constants_buffer_size(current_fragment_program); const size_t fragment_buffer_sz = fragment_constants_sz + (18 * 4 * sizeof(float)); const size_t required_mem = 512 + fragment_buffer_sz; @@ -2384,18 +2413,18 @@ void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info) *(reinterpret_cast(buf + 144)) = rsx::method_registers.clip_max(); fill_vertex_layout_state(m_vertex_layout, vertex_info.allocated_vertex_count, reinterpret_cast(buf + 160), - vertex_info.persistent_window_offset, vertex_info.volatile_window_offset); - + vertex_info.persistent_window_offset, vertex_info.volatile_window_offset); + //Fragment constants buf = buf + 512; if (fragment_constants_sz) { m_prog_buffer->fill_fragment_constants_buffer({ reinterpret_cast(buf), ::narrow(fragment_constants_sz) }, - fragment_program, vk::sanitize_fp_values()); + current_fragment_program, vk::sanitize_fp_values()); } - fill_fragment_state_buffer(buf + fragment_constants_sz, fragment_program); - + fill_fragment_state_buffer(buf + fragment_constants_sz, current_fragment_program); + m_uniform_buffer_ring_info.unmap(); m_vertex_state_buffer_info = { m_uniform_buffer_ring_info.heap->value, vertex_state_offset, 512 }; @@ -2421,7 +2450,8 @@ void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info) } //Clear flags - m_graphics_state &= ~rsx::pipeline_state::memory_barrier_bits; + const u32 handled_flags = (rsx::pipeline_state::fragment_state_dirty | rsx::pipeline_state::vertex_state_dirty | rsx::pipeline_state::transform_constants_dirty); + m_graphics_state &= ~handled_flags; } static const u32 mr_color_offset[rsx::limits::color_buffers_count] = @@ -3408,4 +3438,9 @@ void VKGSRender::discard_occlusion_query(rsx::reports::occlusion_query_info* que m_occlusion_query_pool.reset_queries(*m_current_command_buffer, data.indices); m_occlusion_map.erase(query->driver_handle); +} + +bool VKGSRender::on_decompiler_task() +{ + return m_prog_buffer->async_update(8, *m_device, pipeline_layout); } \ No newline at end of file diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 4db0a003cd..7f0658ec81 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -402,7 +402,8 @@ private: public: bool check_program_status(); - void load_program(const vk::vertex_upload_info& vertex_info); + bool load_program(); + void load_program_env(const vk::vertex_upload_info& vertex_info); void init_buffers(rsx::framebuffer_creation_context context, bool skip_reading = false); void read_buffers(); void write_buffers(); @@ -429,4 +430,6 @@ protected: bool on_access_violation(u32 address, bool is_writing) override; void on_invalidate_memory_range(u32 address_base, u32 size) override; + + bool on_decompiler_task() override; }; diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index 3d971c2fba..a41e60615c 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -2282,10 +2282,13 @@ public: graphics_pipeline_state() { - ia = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; - cs = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; - ds = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; - rs = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + // NOTE: Vk** structs have padding bytes + memset(this, 0, sizeof(graphics_pipeline_state)); + + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + cs.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; for (int i = 0; i < 4; ++i) { @@ -2298,9 +2301,38 @@ public: rs.lineWidth = 1.f; } + graphics_pipeline_state(const graphics_pipeline_state& other) + { + // NOTE: Vk** structs have padding bytes + memcpy(this, &other, sizeof(graphics_pipeline_state)); + + if (other.cs.pAttachments == other.att_state) + { + // Rebase pointer + cs.pAttachments = att_state; + } + } + ~graphics_pipeline_state() {} + graphics_pipeline_state& operator = (const graphics_pipeline_state& other) + { + if (this != &other) + { + // NOTE: Vk** structs have padding bytes + memcpy(this, &other, sizeof(graphics_pipeline_state)); + + if (other.cs.pAttachments == other.att_state) + { + // Rebase pointer + cs.pAttachments = att_state; + } + } + + return *this; + } + void set_primitive_type(VkPrimitiveTopology type) { ia.topology = type; diff --git a/rpcs3/Emu/RSX/VK/VKProgramBuffer.h b/rpcs3/Emu/RSX/VK/VKProgramBuffer.h index 73ce04cae9..8b5f3e862c 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramBuffer.h +++ b/rpcs3/Emu/RSX/VK/VKProgramBuffer.h @@ -53,18 +53,18 @@ namespace rpcs3 template <> size_t hash_struct(const vk::pipeline_props &pipelineProperties) { - size_t seed = hash_base(pipelineProperties.num_targets); + size_t seed = hash_base(pipelineProperties.num_targets); seed ^= hash_struct(pipelineProperties.state.ia); seed ^= hash_struct(pipelineProperties.state.ds); seed ^= hash_struct(pipelineProperties.state.rs); - //Do not compare pointers to memory! - auto tmp = pipelineProperties.state.cs; + // Do not compare pointers to memory! + VkPipelineColorBlendStateCreateInfo tmp; + memcpy(&tmp, &pipelineProperties.state.cs, sizeof(VkPipelineColorBlendStateCreateInfo)); tmp.pAttachments = nullptr; - seed ^= hash_struct(tmp); seed ^= hash_struct(pipelineProperties.state.att_state[0]); - return hash_base(seed); + return hash_base(seed); } } @@ -142,13 +142,17 @@ struct VKTraits ms.pSampleMask = NULL; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + // Rebase pointers from pipeline structure in case it is moved/copied + VkPipelineColorBlendStateCreateInfo cs = pipelineProperties.state.cs; + cs.pAttachments = pipelineProperties.state.att_state; + VkPipeline pipeline; VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.pVertexInputState = &vi; info.pInputAssemblyState = &pipelineProperties.state.ia; info.pRasterizationState = &pipelineProperties.state.rs; - info.pColorBlendState = &pipelineProperties.state.cs; + info.pColorBlendState = &cs; info.pMultisampleState = &ms; info.pViewportState = &vp; info.pDepthStencilState = &pipelineProperties.state.ds; @@ -201,11 +205,9 @@ public: template void add_pipeline_entry(RSXVertexProgram &vp, RSXFragmentProgram &fp, vk::pipeline_props &props, Args&& ...args) { - //Extract pointers from pipeline props props.render_pass = m_render_pass_data[props.render_pass_location]; - props.state.cs.pAttachments = props.state.att_state; vp.skip_vertex_input_check = true; - getGraphicPipelineState(vp, fp, props, std::forward(args)...); + get_graphics_pipeline(vp, fp, props, false, std::forward(args)...); } void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp) @@ -219,4 +221,9 @@ public: { return m_cache_miss_flag; } + + bool check_program_linked_flag() const + { + return m_program_compiled_flag; + } }; diff --git a/rpcs3/Emu/RSX/rsx_cache.h b/rpcs3/Emu/RSX/rsx_cache.h index 91e425cb4e..d8560dbe9f 100644 --- a/rpcs3/Emu/RSX/rsx_cache.h +++ b/rpcs3/Emu/RSX/rsx_cache.h @@ -687,8 +687,7 @@ namespace rsx if (!fs::is_file(fp_name)) { - const auto size = program_hash_util::fragment_program_utils::get_fragment_program_ucode_size(fp.addr); - fs::file(fp_name, fs::rewrite).write(fp.addr, size); + fs::file(fp_name, fs::rewrite).write(fp.addr, fp.ucode_length); } if (!fs::is_file(vp_name)) @@ -741,6 +740,7 @@ namespace rsx RSXFragmentProgram fp = {}; fragment_program_data[program_hash] = data; fp.addr = fragment_program_data[program_hash].data(); + fp.ucode_length = (u32)data.size(); return fp; } diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 6d7107d43d..ddadaa6c1c 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -402,8 +402,9 @@ struct cfg_root : cfg::node cfg::_bool frame_skip_enabled{this, "Enable Frame Skip", false}; cfg::_bool force_cpu_blit_processing{this, "Force CPU Blit", false}; // Debugging option cfg::_bool disable_on_disk_shader_cache{this, "Disable On-Disk Shader Cache", false}; - cfg::_bool disable_vulkan_mem_allocator{ this, "Disable Vulkan Memory Allocator", false }; + cfg::_bool disable_vulkan_mem_allocator{this, "Disable Vulkan Memory Allocator", false}; cfg::_bool full_rgb_range_output{this, "Use full RGB output range", true}; // Video out dynamic range + cfg::_bool disable_asynchronous_shader_compiler{this, "Disable Asynchronous Shader Compiler", false}; cfg::_int<1, 8> consequtive_frames_to_draw{this, "Consecutive Frames To Draw", 1}; cfg::_int<1, 8> consequtive_frames_to_skip{this, "Consecutive Frames To Skip", 1}; cfg::_int<50, 800> resolution_scale_percent{this, "Resolution Scale", 100}; diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index b749408c65..c996e7c593 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -93,6 +93,7 @@ int main(int argc, char** argv) QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); + QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); s_init.post(); s_qt_mutex.wait(); diff --git a/rpcs3/rpcs3qt/gl_gs_frame.cpp b/rpcs3/rpcs3qt/gl_gs_frame.cpp index 26ea770bfd..14b479fa3b 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.cpp +++ b/rpcs3/rpcs3qt/gl_gs_frame.cpp @@ -3,6 +3,7 @@ #include "Emu/System.h" #include +#include #include gl_gs_frame::gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse) @@ -24,42 +25,87 @@ gl_gs_frame::gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse draw_context_t gl_gs_frame::make_context() { - auto context = new QOpenGLContext(); - context->setFormat(m_format); - context->create(); + auto context = new GLContext(); + context->handle = new QOpenGLContext(); + + if (m_primary_context) + { + auto surface = new QOffscreenSurface(); + surface->setFormat(m_format); + surface->create(); + + // Share resources with the first created context + context->handle->setShareContext(m_primary_context->handle); + context->surface = surface; + context->owner = true; + } + else + { + // This is the first created context, all others will share resources with this one + m_primary_context = context; + context->surface = this; + context->owner = false; + } + + context->handle->setFormat(m_format); + context->handle->create(); return context; } void gl_gs_frame::set_current(draw_context_t ctx) { - if (!((QOpenGLContext*)ctx)->makeCurrent(this)) + if (!ctx) { - create(); - ((QOpenGLContext*)ctx)->makeCurrent(this); + fmt::throw_exception("Null context handle passed to set_current" HERE); + } + + auto context = (GLContext*)(ctx); + if (!context->handle->makeCurrent(context->surface)) + { + if (!context->owner) + { + create(); + } + else if (!context->handle->isValid()) + { + context->handle->create(); + } + + if (!context->handle->makeCurrent(context->surface)) + { + fmt::throw_exception("Could not bind OpenGL context" HERE); + } } } void gl_gs_frame::delete_context(draw_context_t ctx) { - auto gl_ctx = (QOpenGLContext*)ctx; - gl_ctx->doneCurrent(); + auto gl_ctx = (GLContext*)ctx; + gl_ctx->handle->doneCurrent(); #ifndef _WIN32 - delete gl_ctx; + delete gl_ctx->handle; #else //AMD driver crashes when executing wglDeleteContext //Catch with SEH __try { - delete gl_ctx; + delete gl_ctx->handle; } __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { LOG_FATAL(RSX, "Your graphics driver just crashed whilst cleaning up. All consumed VRAM should have been released, but you may want to restart the emulator just in case"); } #endif + + if (gl_ctx->owner) + { + delete gl_ctx->surface; + } + + delete gl_ctx; } void gl_gs_frame::flip(draw_context_t context, bool skip_frame) @@ -69,5 +115,6 @@ void gl_gs_frame::flip(draw_context_t context, bool skip_frame) //Do not swap buffers if frame skip is active if (skip_frame) return; - ((QOpenGLContext*)context)->swapBuffers(this); + auto gl_ctx = (GLContext*)context; + gl_ctx->handle->swapBuffers(gl_ctx->surface); } diff --git a/rpcs3/rpcs3qt/gl_gs_frame.h b/rpcs3/rpcs3qt/gl_gs_frame.h index c5ddfadc23..2f44be37e8 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.h +++ b/rpcs3/rpcs3qt/gl_gs_frame.h @@ -3,10 +3,18 @@ #include "stdafx.h" #include "gs_frame.h" +struct GLContext +{ + QSurface *surface = nullptr; + QOpenGLContext *handle = nullptr; + bool owner = false; +}; + class gl_gs_frame : public gs_frame { private: QSurfaceFormat m_format; + GLContext *m_primary_context = nullptr; public: gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse);