mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-01-31 06:32:41 +00:00
rsx: Async shader compilation
- Defer compilation process to worker threads - vulkan: Fixup for graphics_pipeline_state. Never use struct assignment operator on vk** structs due to padding after sType member (4 bytes)
This commit is contained in:
parent
ac99fd764d
commit
e7f30640ef
@ -335,7 +335,7 @@ public:
|
|||||||
u32 ctrl = (vmfprog.outputFromH0 ? 0 : 0x40) | (vmfprog.depthReplace ? 0xe : 0);
|
u32 ctrl = (vmfprog.outputFromH0 ? 0 : 0x40) | (vmfprog.depthReplace ? 0xe : 0);
|
||||||
std::vector<rsx::texture_dimension_extended> td;
|
std::vector<rsx::texture_dimension_extended> td;
|
||||||
RSXFragmentProgram prog;
|
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();
|
GLFragmentDecompilerThread(m_glsl_shader, param_array, prog, size).Task();
|
||||||
vm::close();
|
vm::close();
|
||||||
}
|
}
|
||||||
|
@ -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)
|
fragment_program_utils::fragment_program_metadata fragment_program_utils::analyse_fragment_program(void *ptr)
|
||||||
{
|
{
|
||||||
const qword *instBuffer = (const qword*)ptr;
|
const qword *instBuffer = (const qword*)ptr;
|
||||||
size_t instIndex = 0;
|
s32 index = 0;
|
||||||
s32 program_offset = -1;
|
s32 program_offset = -1;
|
||||||
u32 ucode_size = 0;
|
u32 ucode_size = 0;
|
||||||
u16 textures_mask = 0;
|
u16 textures_mask = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
const qword& inst = instBuffer[instIndex];
|
const qword& inst = instBuffer[index];
|
||||||
const u32 opcode = (inst.word[0] >> 16) & 0x3F;
|
const u32 opcode = (inst.word[0] >> 16) & 0x3F;
|
||||||
|
|
||||||
if (opcode)
|
if (opcode)
|
||||||
{
|
{
|
||||||
if (program_offset < 0)
|
if (program_offset < 0)
|
||||||
program_offset = instIndex * 16;
|
program_offset = index * 16;
|
||||||
|
|
||||||
switch(opcode)
|
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]))
|
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
|
//Instruction references constant, skip one slot occupied by data
|
||||||
instIndex++;
|
index++;
|
||||||
ucode_size += 16;
|
ucode_size += 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,14 +376,14 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys
|
|||||||
{
|
{
|
||||||
if (program_offset < 0)
|
if (program_offset < 0)
|
||||||
{
|
{
|
||||||
program_offset = instIndex * 16;
|
program_offset = index * 16;
|
||||||
ucode_size = 16;
|
ucode_size = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
instIndex++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return{ (u32)program_offset, ucode_size, textures_mask };
|
return{ (u32)program_offset, ucode_size, textures_mask };
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
#include "Utilities/GSL.h"
|
#include "Utilities/GSL.h"
|
||||||
#include "Utilities/hash.h"
|
#include "Utilities/hash.h"
|
||||||
#include <mutex>
|
#include "Utilities/mutex.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
enum class SHADER_TYPE
|
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<u8> 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:
|
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;
|
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_vertex_program m_vertex_shader_cache;
|
||||||
binary_to_fragment_program m_fragment_shader_cache;
|
binary_to_fragment_program m_fragment_shader_cache;
|
||||||
std::unordered_map <pipeline_key, pipeline_storage_type, pipeline_key_hash, pipeline_key_compare> m_storage;
|
std::unordered_map <pipeline_key, pipeline_storage_type, pipeline_key_hash, pipeline_key_compare> m_storage;
|
||||||
|
|
||||||
|
std::unordered_map <pipeline_key, std::unique_ptr<async_link_task_entry>, pipeline_key_hash, pipeline_key_compare> m_link_queue;
|
||||||
|
std::deque<async_decompile_task_entry> 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.
|
/// bool here to inform that the program was preexisting.
|
||||||
std::tuple<const vertex_program_type&, bool> search_vertex_program(const RSXVertexProgram& rsx_vp)
|
std::tuple<const vertex_program_type&, bool> search_vertex_program(const RSXVertexProgram& rsx_vp, bool force_load = true)
|
||||||
{
|
{
|
||||||
const auto& I = m_vertex_shader_cache.find(rsx_vp);
|
const auto& I = m_vertex_shader_cache.find(rsx_vp);
|
||||||
if (I != m_vertex_shader_cache.end())
|
if (I != m_vertex_shader_cache.end())
|
||||||
{
|
{
|
||||||
return std::forward_as_tuple(I->second, true);
|
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!");
|
LOG_NOTICE(RSX, "VP not found in buffer!");
|
||||||
vertex_program_type& new_shader = m_vertex_shader_cache[rsx_vp];
|
vertex_program_type& new_shader = m_vertex_shader_cache[rsx_vp];
|
||||||
backend_traits::recompile_vertex_program(rsx_vp, new_shader, m_next_id++);
|
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.
|
/// bool here to inform that the program was preexisting.
|
||||||
std::tuple<const fragment_program_type&, bool> search_fragment_program(const RSXFragmentProgram& rsx_fp)
|
std::tuple<const fragment_program_type&, bool> search_fragment_program(const RSXFragmentProgram& rsx_fp, bool force_load = true)
|
||||||
{
|
{
|
||||||
const auto& I = m_fragment_shader_cache.find(rsx_fp);
|
const auto& I = m_fragment_shader_cache.find(rsx_fp);
|
||||||
if (I != m_fragment_shader_cache.end())
|
if (I != m_fragment_shader_cache.end())
|
||||||
{
|
{
|
||||||
return std::forward_as_tuple(I->second, true);
|
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!");
|
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<void*> fragment_program_ucode_copy = malloc(rsx_fp.ucode_length);
|
||||||
gsl::not_null<void*> fragment_program_ucode_copy = malloc(fragment_program_size);
|
std::memcpy(fragment_program_ucode_copy, rsx_fp.addr, rsx_fp.ucode_length);
|
||||||
std::memcpy(fragment_program_ucode_copy, rsx_fp.addr, fragment_program_size);
|
|
||||||
RSXFragmentProgram new_fp_key = rsx_fp;
|
RSXFragmentProgram new_fp_key = rsx_fp;
|
||||||
new_fp_key.addr = fragment_program_ucode_copy;
|
new_fp_key.addr = fragment_program_ucode_copy;
|
||||||
fragment_program_type &new_shader = m_fragment_shader_cache[new_fp_key];
|
fragment_program_type &new_shader = m_fragment_shader_cache[new_fp_key];
|
||||||
@ -277,45 +335,173 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
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>(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<typename... Args>
|
||||||
|
pipeline_storage_type& get_graphics_pipeline(
|
||||||
const RSXVertexProgram& vertexShader,
|
const RSXVertexProgram& vertexShader,
|
||||||
const RSXFragmentProgram& fragmentShader,
|
const RSXFragmentProgram& fragmentShader,
|
||||||
pipeline_properties& pipelineProperties,
|
pipeline_properties& pipelineProperties,
|
||||||
|
bool allow_async,
|
||||||
Args&& ...args
|
Args&& ...args
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// TODO : use tie and implicit variable declaration syntax with c++17
|
const auto &vp_search = search_vertex_program(vertexShader, !allow_async);
|
||||||
const auto &vp_search = search_vertex_program(vertexShader);
|
const auto &fp_search = search_fragment_program(fragmentShader, !allow_async);
|
||||||
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);
|
|
||||||
|
|
||||||
backend_traits::validate_pipeline_properties(vertex_program, fragment_program, pipelineProperties);
|
const bool already_existing_fragment_program = std::get<1>(fp_search);
|
||||||
pipeline_key key = { vertex_program.id, fragment_program.id, pipelineProperties };
|
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);
|
const auto I = m_storage.find(key);
|
||||||
if (I != m_storage.end())
|
if (I != m_storage.end())
|
||||||
{
|
{
|
||||||
m_cache_miss_flag = false;
|
m_cache_miss_flag = false;
|
||||||
return I->second;
|
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>(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 :");
|
verify(HERE), allow_async;
|
||||||
LOG_NOTICE(RSX, "*** vp id = %d", vertex_program.id);
|
|
||||||
LOG_NOTICE(RSX, "*** fp id = %d", fragment_program.id);
|
|
||||||
|
|
||||||
pipeline_storage_type pipeline = backend_traits::build_pipeline(vertex_program, fragment_program, pipelineProperties, std::forward<Args>(args)...);
|
if (link_only)
|
||||||
std::lock_guard<std::mutex> lock(s_mtx);
|
{
|
||||||
auto &rtn = m_storage[key] = std::move(pipeline);
|
const vertex_program_type &vertex_program = std::get<0>(vp_search);
|
||||||
m_cache_miss_flag = true;
|
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");
|
reader_lock lock(m_pipeline_mutex);
|
||||||
return rtn;
|
|
||||||
|
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<async_link_task_entry>(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
|
size_t get_fragment_constants_buffer_size(const RSXFragmentProgram &fragmentShader) const
|
||||||
|
@ -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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,9 +372,19 @@ void GLGSRender::end()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::time_point<steady_clock> program_start = steady_clock::now();
|
std::chrono::time_point<steady_clock> 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<steady_clock> program_stop = steady_clock::now();
|
std::chrono::time_point<steady_clock> program_stop = steady_clock::now();
|
||||||
m_begin_time += (u32)std::chrono::duration_cast<std::chrono::microseconds>(program_stop - program_start).count();
|
m_begin_time += (u32)std::chrono::duration_cast<std::chrono::microseconds>(program_stop - program_start).count();
|
||||||
@ -619,7 +629,16 @@ void GLGSRender::set_viewport()
|
|||||||
|
|
||||||
void GLGSRender::on_init_thread()
|
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));
|
zcull_ctrl.reset(static_cast<::rsx::reports::ZCULL_control*>(this));
|
||||||
|
|
||||||
gl::init();
|
gl::init();
|
||||||
@ -1108,7 +1127,7 @@ bool GLGSRender::check_program_state()
|
|||||||
return (rsx::method_registers.shader_program_address() != 0);
|
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)
|
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_vertex_program.skip_vertex_input_check = true; //not needed for us since decoding is done server side
|
||||||
current_fragment_program.unnormalized_coords = 0; //unused
|
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);
|
void* pipeline_properties = nullptr;
|
||||||
m_program->use();
|
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);
|
m_shaders_cache->store(pipeline_properties, current_vertex_program, current_fragment_program);
|
||||||
|
}
|
||||||
|
|
||||||
//Notify the user with HUD notification
|
// Notify the user with HUD notification
|
||||||
if (g_cfg.misc.show_shader_compilation_hint)
|
if (g_cfg.misc.show_shader_compilation_hint)
|
||||||
|
{
|
||||||
|
if (m_overlay_manager)
|
||||||
{
|
{
|
||||||
if (m_overlay_manager)
|
if (auto dlg = m_overlay_manager->get<rsx::overlays::shader_compile_notification>())
|
||||||
{
|
{
|
||||||
if (auto dlg = m_overlay_manager->get<rsx::overlays::shader_compile_notification>())
|
// Extend duration
|
||||||
{
|
dlg->touch();
|
||||||
//Extend duration
|
}
|
||||||
dlg->touch();
|
else
|
||||||
}
|
{
|
||||||
else
|
// Create dialog but do not show immediately
|
||||||
{
|
m_overlay_manager->create<rsx::overlays::shader_compile_notification>();
|
||||||
//Create dialog but do not show immediately
|
|
||||||
m_overlay_manager->create<rsx::overlays::shader_compile_notification>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return m_program != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGSRender::load_program_env(const gl::vertex_upload_info& upload_info)
|
||||||
|
{
|
||||||
u8 *buf;
|
u8 *buf;
|
||||||
u32 vertex_state_offset;
|
u32 vertex_state_offset;
|
||||||
u32 vertex_constants_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 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);
|
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)
|
if (manually_flush_ring_buffers)
|
||||||
{
|
{
|
||||||
m_vertex_state_buffer->reserve_storage_on_heap(512);
|
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();
|
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()
|
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);
|
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;
|
||||||
|
}
|
||||||
|
@ -329,6 +329,7 @@ private:
|
|||||||
std::thread::id m_thread_id;
|
std::thread::id m_thread_id;
|
||||||
|
|
||||||
GLProgramBuffer m_prog_buffer;
|
GLProgramBuffer m_prog_buffer;
|
||||||
|
draw_context_t m_decompiler_context;
|
||||||
|
|
||||||
//buffer
|
//buffer
|
||||||
gl::fbo draw_fbo;
|
gl::fbo draw_fbo;
|
||||||
@ -361,7 +362,8 @@ private:
|
|||||||
void init_buffers(rsx::framebuffer_creation_context context, bool skip_reading = false);
|
void init_buffers(rsx::framebuffer_creation_context context, bool skip_reading = false);
|
||||||
|
|
||||||
bool check_program_state();
|
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();
|
void update_draw_state();
|
||||||
|
|
||||||
@ -398,4 +400,8 @@ protected:
|
|||||||
|
|
||||||
std::array<std::vector<gsl::byte>, 4> copy_render_targets_to_memory() override;
|
std::array<std::vector<gsl::byte>, 4> copy_render_targets_to_memory() override;
|
||||||
std::array<std::vector<gsl::byte>, 2> copy_depth_stencil_buffer_to_memory() override;
|
std::array<std::vector<gsl::byte>, 2> copy_depth_stencil_buffer_to_memory() override;
|
||||||
|
|
||||||
|
void on_decompiler_init() override;
|
||||||
|
void on_decompiler_exit() override;
|
||||||
|
bool on_decompiler_task() override;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ struct GLTraits
|
|||||||
{
|
{
|
||||||
using vertex_program_type = GLVertexProgram;
|
using vertex_program_type = GLVertexProgram;
|
||||||
using fragment_program_type = GLFragmentProgram;
|
using fragment_program_type = GLFragmentProgram;
|
||||||
using pipeline_storage_type = gl::glsl::program;
|
using pipeline_storage_type = std::unique_ptr<gl::glsl::program>;
|
||||||
using pipeline_properties = void*;
|
using pipeline_properties = void*;
|
||||||
|
|
||||||
static
|
static
|
||||||
@ -32,8 +32,8 @@ struct GLTraits
|
|||||||
static
|
static
|
||||||
pipeline_storage_type build_pipeline(const vertex_program_type &vertexProgramData, const fragment_program_type &fragmentProgramData, const pipeline_properties&)
|
pipeline_storage_type build_pipeline(const vertex_program_type &vertexProgramData, const fragment_program_type &fragmentProgramData, const pipeline_properties&)
|
||||||
{
|
{
|
||||||
pipeline_storage_type result;
|
pipeline_storage_type result = std::make_unique<gl::glsl::program>();
|
||||||
__glcheck result.create()
|
result->create()
|
||||||
.attach(gl::glsl::shader_view(vertexProgramData.id))
|
.attach(gl::glsl::shader_view(vertexProgramData.id))
|
||||||
.attach(gl::glsl::shader_view(fragmentProgramData.id))
|
.attach(gl::glsl::shader_view(fragmentProgramData.id))
|
||||||
.bind_fragment_data_location("ocol0", 0)
|
.bind_fragment_data_location("ocol0", 0)
|
||||||
@ -41,31 +41,30 @@ struct GLTraits
|
|||||||
.bind_fragment_data_location("ocol2", 2)
|
.bind_fragment_data_location("ocol2", 2)
|
||||||
.bind_fragment_data_location("ocol3", 3)
|
.bind_fragment_data_location("ocol3", 3)
|
||||||
.make();
|
.make();
|
||||||
__glcheck result.use();
|
|
||||||
|
|
||||||
//Progam locations are guaranteed to not change after linking
|
//Progam locations are guaranteed to not change after linking
|
||||||
//Texture locations are simply bound to the TIUs so this can be done once
|
//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)
|
for (int i = 0; i < rsx::limits::fragment_textures_count; ++i)
|
||||||
{
|
{
|
||||||
int location;
|
int location;
|
||||||
if (result.uniforms.has_location(rsx::constants::fragment_texture_names[i], &location))
|
if (result->uniforms.has_location(rsx::constants::fragment_texture_names[i], &location))
|
||||||
result.uniforms[location] = i;
|
result->uniforms[location] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < rsx::limits::vertex_textures_count; ++i)
|
for (int i = 0; i < rsx::limits::vertex_textures_count; ++i)
|
||||||
{
|
{
|
||||||
int location;
|
int location;
|
||||||
if (result.uniforms.has_location(rsx::constants::vertex_texture_names[i], &location))
|
if (result->uniforms.has_location(rsx::constants::vertex_texture_names[i], &location))
|
||||||
result.uniforms[location] = (i + rsx::limits::fragment_textures_count);
|
result->uniforms[location] = (i + rsx::limits::fragment_textures_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int stream_buffer_start = rsx::limits::fragment_textures_count + rsx::limits::vertex_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
|
//Bind locations 0 and 1 to the stream buffers
|
||||||
result.uniforms[0] = stream_buffer_start;
|
result->uniforms[0] = stream_buffer_start;
|
||||||
result.uniforms[1] = stream_buffer_start + 1;
|
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, "*** vp id = %d", vertexProgramData.id);
|
||||||
LOG_NOTICE(RSX, "*** fp id = %d", fragmentProgramData.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)
|
void add_pipeline_entry(RSXVertexProgram &vp, RSXFragmentProgram &fp, void* &props, Args&& ...args)
|
||||||
{
|
{
|
||||||
vp.skip_vertex_input_check = true;
|
vp.skip_vertex_input_check = true;
|
||||||
getGraphicPipelineState(vp, fp, props, std::forward<Args>(args)...);
|
get_graphics_pipeline(vp, fp, props, false, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp)
|
void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp)
|
||||||
@ -112,4 +111,9 @@ public:
|
|||||||
{
|
{
|
||||||
return m_cache_miss_flag;
|
return m_cache_miss_flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_program_linked_flag() const
|
||||||
|
{
|
||||||
|
return m_program_compiled_flag;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,11 @@ namespace rsx
|
|||||||
{
|
{
|
||||||
if (auto rsxthr = rsx::get_current_renderer())
|
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
|
} // namespace overlays
|
||||||
|
@ -32,6 +32,8 @@ namespace rsx
|
|||||||
u16 virtual_width = 1280;
|
u16 virtual_width = 1280;
|
||||||
u16 virtual_height = 720;
|
u16 virtual_height = 720;
|
||||||
|
|
||||||
|
u32 min_refresh_duration_us = 16600;
|
||||||
|
|
||||||
virtual ~overlay() = default;
|
virtual ~overlay() = default;
|
||||||
|
|
||||||
virtual void update() {}
|
virtual void update() {}
|
||||||
@ -1103,6 +1105,9 @@ namespace rsx
|
|||||||
|
|
||||||
creation_time = get_system_time();
|
creation_time = get_system_time();
|
||||||
expire_time = creation_time + 1000000;
|
expire_time = creation_time + 1000000;
|
||||||
|
|
||||||
|
// Disable forced refresh unless fps dips below 4
|
||||||
|
min_refresh_duration_us = 250000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_animation(u64 t)
|
void update_animation(u64 t)
|
||||||
|
@ -216,9 +216,9 @@ static const std::string rsx_fp_op_names[] =
|
|||||||
|
|
||||||
struct RSXFragmentProgram
|
struct RSXFragmentProgram
|
||||||
{
|
{
|
||||||
u32 size;
|
|
||||||
void *addr;
|
void *addr;
|
||||||
u32 offset;
|
u32 offset;
|
||||||
|
u32 ucode_length;
|
||||||
u32 ctrl;
|
u32 ctrl;
|
||||||
u16 unnormalized_coords;
|
u16 unnormalized_coords;
|
||||||
u16 redirected_textures;
|
u16 redirected_textures;
|
||||||
|
@ -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
|
// Raise priority above other threads
|
||||||
thread_ctrl::set_native_priority(1);
|
thread_ctrl::set_native_priority(1);
|
||||||
|
|
||||||
@ -969,6 +1010,12 @@ namespace rsx
|
|||||||
m_vblank_thread->join();
|
m_vblank_thread->join();
|
||||||
m_vblank_thread.reset();
|
m_vblank_thread.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_decompiler_thread)
|
||||||
|
{
|
||||||
|
m_decompiler_thread->join();
|
||||||
|
m_decompiler_thread.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string thread::get_name() const
|
std::string thread::get_name() const
|
||||||
@ -1651,6 +1698,7 @@ namespace rsx
|
|||||||
|
|
||||||
result.addr = ((u8*)result.addr + current_fp_metadata.program_start_offset);
|
result.addr = ((u8*)result.addr + current_fp_metadata.program_start_offset);
|
||||||
result.offset = program_offset + 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.valid = true;
|
||||||
result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
||||||
result.unnormalized_coords = 0;
|
result.unnormalized_coords = 0;
|
||||||
@ -1781,6 +1829,7 @@ namespace rsx
|
|||||||
|
|
||||||
result.addr = ((u8*)result.addr + program_info.program_start_offset);
|
result.addr = ((u8*)result.addr + program_info.program_start_offset);
|
||||||
result.offset = program_offset + 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.valid = true;
|
||||||
result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
result.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
||||||
result.unnormalized_coords = 0;
|
result.unnormalized_coords = 0;
|
||||||
|
@ -275,6 +275,7 @@ namespace rsx
|
|||||||
class thread : public named_thread
|
class thread : public named_thread
|
||||||
{
|
{
|
||||||
std::shared_ptr<thread_ctrl> m_vblank_thread;
|
std::shared_ptr<thread_ctrl> m_vblank_thread;
|
||||||
|
std::shared_ptr<thread_ctrl> m_decompiler_thread;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
atomic_t<bool> m_rsx_thread_exiting{false};
|
atomic_t<bool> m_rsx_thread_exiting{false};
|
||||||
@ -424,6 +425,10 @@ namespace rsx
|
|||||||
*/
|
*/
|
||||||
virtual void do_local_task(FIFO_state state);
|
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:
|
public:
|
||||||
virtual std::string get_name() const override;
|
virtual std::string get_name() const override;
|
||||||
|
|
||||||
|
@ -1066,13 +1066,7 @@ void VKGSRender::end()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Programs data is dependent on vertex state
|
std::chrono::time_point<steady_clock> textures_start = steady_clock::now();
|
||||||
std::chrono::time_point<steady_clock> vertex_start = steady_clock::now();
|
|
||||||
auto upload_info = upload_vertex_data();
|
|
||||||
std::chrono::time_point<steady_clock> vertex_end = steady_clock::now();
|
|
||||||
m_vertex_upload_time += std::chrono::duration_cast<std::chrono::microseconds>(vertex_end - vertex_start).count();
|
|
||||||
|
|
||||||
std::chrono::time_point<steady_clock> textures_start = vertex_end;
|
|
||||||
|
|
||||||
auto ds = std::get<1>(m_rtts.m_bound_depth_stencil);
|
auto ds = std::get<1>(m_rtts.m_bound_depth_stencil);
|
||||||
//Clear any 'dirty' surfaces - possible is a recycled cache surface is used
|
//Clear any 'dirty' surfaces - possible is a recycled cache surface is used
|
||||||
@ -1303,19 +1297,37 @@ void VKGSRender::end()
|
|||||||
std::chrono::time_point<steady_clock> textures_end = steady_clock::now();
|
std::chrono::time_point<steady_clock> textures_end = steady_clock::now();
|
||||||
m_textures_upload_time += (u32)std::chrono::duration_cast<std::chrono::microseconds>(textures_end - textures_start).count();
|
m_textures_upload_time += (u32)std::chrono::duration_cast<std::chrono::microseconds>(textures_end - textures_start).count();
|
||||||
|
|
||||||
//Load program
|
|
||||||
std::chrono::time_point<steady_clock> program_start = textures_end;
|
std::chrono::time_point<steady_clock> 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<steady_clock> program_end = steady_clock::now();
|
||||||
|
m_setup_time += std::chrono::duration_cast<std::chrono::microseconds>(program_end - program_start).count();
|
||||||
|
|
||||||
|
// Programs data is dependent on vertex state
|
||||||
|
std::chrono::time_point<steady_clock> vertex_start = program_end;
|
||||||
|
auto upload_info = upload_vertex_data();
|
||||||
|
std::chrono::time_point<steady_clock> vertex_end = steady_clock::now();
|
||||||
|
m_vertex_upload_time += std::chrono::duration_cast<std::chrono::microseconds>(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 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;
|
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(persistent_buffer, "persistent_input_stream", m_current_frame->descriptor_set);
|
||||||
m_program->bind_uniform(volatile_buffer, "volatile_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<steady_clock> program_stop = steady_clock::now();
|
program_end = steady_clock::now();
|
||||||
m_setup_time += std::chrono::duration_cast<std::chrono::microseconds>(program_stop - program_start).count();
|
m_setup_time += std::chrono::duration_cast<std::chrono::microseconds>(program_end - program_start).count();
|
||||||
|
|
||||||
textures_start = program_stop;
|
textures_start = program_end;
|
||||||
|
|
||||||
for (int i = 0; i < rsx::limits::fragment_textures_count; ++i)
|
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);
|
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)
|
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;
|
verify(HERE), current_fragment_program.valid;
|
||||||
|
|
||||||
get_current_vertex_program(vs_sampler_state);
|
get_current_vertex_program(vs_sampler_state);
|
||||||
|
|
||||||
|
m_graphics_state &= ~rsx::pipeline_state::invalidate_pipeline_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &vertex_program = current_vertex_program;
|
auto &vertex_program = current_vertex_program;
|
||||||
auto &fragment_program = current_fragment_program;
|
auto &fragment_program = current_fragment_program;
|
||||||
auto old_program = m_program;
|
auto old_program = m_program;
|
||||||
|
|
||||||
vk::pipeline_props properties = {};
|
vk::pipeline_props properties{};
|
||||||
|
|
||||||
// Input assembly
|
// Input assembly
|
||||||
bool emulated_primitive_type;
|
bool emulated_primitive_type;
|
||||||
@ -2335,36 +2349,51 @@ void VKGSRender::load_program(const vk::vertex_upload_info& vertex_info)
|
|||||||
//Load current program from buffer
|
//Load current program from buffer
|
||||||
vertex_program.skip_vertex_input_check = true;
|
vertex_program.skip_vertex_input_check = true;
|
||||||
fragment_program.unnormalized_coords = 0;
|
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())
|
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 (g_cfg.misc.show_shader_compilation_hint)
|
||||||
{
|
{
|
||||||
if (m_overlay_manager)
|
if (m_overlay_manager)
|
||||||
{
|
{
|
||||||
if (auto dlg = m_overlay_manager->get<rsx::overlays::shader_compile_notification>())
|
if (auto dlg = m_overlay_manager->get<rsx::overlays::shader_compile_notification>())
|
||||||
{
|
{
|
||||||
//Extend duration
|
// Extend duration
|
||||||
dlg->touch();
|
dlg->touch();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Create dialog but do not show immediately
|
// Create dialog but do not show immediately
|
||||||
m_overlay_manager->create<rsx::overlays::shader_compile_notification>();
|
m_overlay_manager->create<rsx::overlays::shader_compile_notification>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
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 fragment_buffer_sz = fragment_constants_sz + (18 * 4 * sizeof(float));
|
||||||
const size_t required_mem = 512 + fragment_buffer_sz;
|
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<f32*>(buf + 144)) = rsx::method_registers.clip_max();
|
*(reinterpret_cast<f32*>(buf + 144)) = rsx::method_registers.clip_max();
|
||||||
|
|
||||||
fill_vertex_layout_state(m_vertex_layout, vertex_info.allocated_vertex_count, reinterpret_cast<s32*>(buf + 160),
|
fill_vertex_layout_state(m_vertex_layout, vertex_info.allocated_vertex_count, reinterpret_cast<s32*>(buf + 160),
|
||||||
vertex_info.persistent_window_offset, vertex_info.volatile_window_offset);
|
vertex_info.persistent_window_offset, vertex_info.volatile_window_offset);
|
||||||
|
|
||||||
//Fragment constants
|
//Fragment constants
|
||||||
buf = buf + 512;
|
buf = buf + 512;
|
||||||
if (fragment_constants_sz)
|
if (fragment_constants_sz)
|
||||||
{
|
{
|
||||||
m_prog_buffer->fill_fragment_constants_buffer({ reinterpret_cast<float*>(buf), ::narrow<int>(fragment_constants_sz) },
|
m_prog_buffer->fill_fragment_constants_buffer({ reinterpret_cast<float*>(buf), ::narrow<int>(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_uniform_buffer_ring_info.unmap();
|
||||||
|
|
||||||
m_vertex_state_buffer_info = { m_uniform_buffer_ring_info.heap->value, vertex_state_offset, 512 };
|
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
|
//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] =
|
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_query_pool.reset_queries(*m_current_command_buffer, data.indices);
|
||||||
m_occlusion_map.erase(query->driver_handle);
|
m_occlusion_map.erase(query->driver_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VKGSRender::on_decompiler_task()
|
||||||
|
{
|
||||||
|
return m_prog_buffer->async_update(8, *m_device, pipeline_layout);
|
||||||
}
|
}
|
@ -402,7 +402,8 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
bool check_program_status();
|
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 init_buffers(rsx::framebuffer_creation_context context, bool skip_reading = false);
|
||||||
void read_buffers();
|
void read_buffers();
|
||||||
void write_buffers();
|
void write_buffers();
|
||||||
@ -429,4 +430,6 @@ protected:
|
|||||||
|
|
||||||
bool on_access_violation(u32 address, bool is_writing) override;
|
bool on_access_violation(u32 address, bool is_writing) override;
|
||||||
void on_invalidate_memory_range(u32 address_base, u32 size) override;
|
void on_invalidate_memory_range(u32 address_base, u32 size) override;
|
||||||
|
|
||||||
|
bool on_decompiler_task() override;
|
||||||
};
|
};
|
||||||
|
@ -2282,10 +2282,13 @@ public:
|
|||||||
|
|
||||||
graphics_pipeline_state()
|
graphics_pipeline_state()
|
||||||
{
|
{
|
||||||
ia = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
|
// NOTE: Vk** structs have padding bytes
|
||||||
cs = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
|
memset(this, 0, sizeof(graphics_pipeline_state));
|
||||||
ds = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };
|
|
||||||
rs = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
|
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)
|
for (int i = 0; i < 4; ++i)
|
||||||
{
|
{
|
||||||
@ -2298,9 +2301,38 @@ public:
|
|||||||
rs.lineWidth = 1.f;
|
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()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
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)
|
void set_primitive_type(VkPrimitiveTopology type)
|
||||||
{
|
{
|
||||||
ia.topology = type;
|
ia.topology = type;
|
||||||
|
@ -53,18 +53,18 @@ namespace rpcs3
|
|||||||
template <>
|
template <>
|
||||||
size_t hash_struct<vk::pipeline_props>(const vk::pipeline_props &pipelineProperties)
|
size_t hash_struct<vk::pipeline_props>(const vk::pipeline_props &pipelineProperties)
|
||||||
{
|
{
|
||||||
size_t seed = hash_base<int>(pipelineProperties.num_targets);
|
size_t seed = hash_base(pipelineProperties.num_targets);
|
||||||
seed ^= hash_struct(pipelineProperties.state.ia);
|
seed ^= hash_struct(pipelineProperties.state.ia);
|
||||||
seed ^= hash_struct(pipelineProperties.state.ds);
|
seed ^= hash_struct(pipelineProperties.state.ds);
|
||||||
seed ^= hash_struct(pipelineProperties.state.rs);
|
seed ^= hash_struct(pipelineProperties.state.rs);
|
||||||
|
|
||||||
//Do not compare pointers to memory!
|
// Do not compare pointers to memory!
|
||||||
auto tmp = pipelineProperties.state.cs;
|
VkPipelineColorBlendStateCreateInfo tmp;
|
||||||
|
memcpy(&tmp, &pipelineProperties.state.cs, sizeof(VkPipelineColorBlendStateCreateInfo));
|
||||||
tmp.pAttachments = nullptr;
|
tmp.pAttachments = nullptr;
|
||||||
seed ^= hash_struct(tmp);
|
|
||||||
|
|
||||||
seed ^= hash_struct(pipelineProperties.state.att_state[0]);
|
seed ^= hash_struct(pipelineProperties.state.att_state[0]);
|
||||||
return hash_base<size_t>(seed);
|
return hash_base(seed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,13 +142,17 @@ struct VKTraits
|
|||||||
ms.pSampleMask = NULL;
|
ms.pSampleMask = NULL;
|
||||||
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
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;
|
VkPipeline pipeline;
|
||||||
VkGraphicsPipelineCreateInfo info = {};
|
VkGraphicsPipelineCreateInfo info = {};
|
||||||
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||||
info.pVertexInputState = &vi;
|
info.pVertexInputState = &vi;
|
||||||
info.pInputAssemblyState = &pipelineProperties.state.ia;
|
info.pInputAssemblyState = &pipelineProperties.state.ia;
|
||||||
info.pRasterizationState = &pipelineProperties.state.rs;
|
info.pRasterizationState = &pipelineProperties.state.rs;
|
||||||
info.pColorBlendState = &pipelineProperties.state.cs;
|
info.pColorBlendState = &cs;
|
||||||
info.pMultisampleState = &ms;
|
info.pMultisampleState = &ms;
|
||||||
info.pViewportState = &vp;
|
info.pViewportState = &vp;
|
||||||
info.pDepthStencilState = &pipelineProperties.state.ds;
|
info.pDepthStencilState = &pipelineProperties.state.ds;
|
||||||
@ -201,11 +205,9 @@ public:
|
|||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void add_pipeline_entry(RSXVertexProgram &vp, RSXFragmentProgram &fp, vk::pipeline_props &props, Args&& ...args)
|
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.render_pass = m_render_pass_data[props.render_pass_location];
|
||||||
props.state.cs.pAttachments = props.state.att_state;
|
|
||||||
vp.skip_vertex_input_check = true;
|
vp.skip_vertex_input_check = true;
|
||||||
getGraphicPipelineState(vp, fp, props, std::forward<Args>(args)...);
|
get_graphics_pipeline(vp, fp, props, false, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp)
|
void preload_programs(RSXVertexProgram &vp, RSXFragmentProgram &fp)
|
||||||
@ -219,4 +221,9 @@ public:
|
|||||||
{
|
{
|
||||||
return m_cache_miss_flag;
|
return m_cache_miss_flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_program_linked_flag() const
|
||||||
|
{
|
||||||
|
return m_program_compiled_flag;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -687,8 +687,7 @@ namespace rsx
|
|||||||
|
|
||||||
if (!fs::is_file(fp_name))
|
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, fp.ucode_length);
|
||||||
fs::file(fp_name, fs::rewrite).write(fp.addr, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs::is_file(vp_name))
|
if (!fs::is_file(vp_name))
|
||||||
@ -741,6 +740,7 @@ namespace rsx
|
|||||||
RSXFragmentProgram fp = {};
|
RSXFragmentProgram fp = {};
|
||||||
fragment_program_data[program_hash] = data;
|
fragment_program_data[program_hash] = data;
|
||||||
fp.addr = fragment_program_data[program_hash].data();
|
fp.addr = fragment_program_data[program_hash].data();
|
||||||
|
fp.ucode_length = (u32)data.size();
|
||||||
|
|
||||||
return fp;
|
return fp;
|
||||||
}
|
}
|
||||||
|
@ -402,8 +402,9 @@ struct cfg_root : cfg::node
|
|||||||
cfg::_bool frame_skip_enabled{this, "Enable Frame Skip", false};
|
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 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_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 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_draw{this, "Consecutive Frames To Draw", 1};
|
||||||
cfg::_int<1, 8> consequtive_frames_to_skip{this, "Consecutive Frames To Skip", 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};
|
cfg::_int<50, 800> resolution_scale_percent{this, "Resolution Scale", 100};
|
||||||
|
@ -93,6 +93,7 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||||
QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
||||||
|
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
||||||
|
|
||||||
s_init.post();
|
s_init.post();
|
||||||
s_qt_mutex.wait();
|
s_qt_mutex.wait();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "Emu/System.h"
|
#include "Emu/System.h"
|
||||||
|
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
|
#include <qoffscreensurface.h>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
|
||||||
gl_gs_frame::gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse)
|
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()
|
draw_context_t gl_gs_frame::make_context()
|
||||||
{
|
{
|
||||||
auto context = new QOpenGLContext();
|
auto context = new GLContext();
|
||||||
context->setFormat(m_format);
|
context->handle = new QOpenGLContext();
|
||||||
context->create();
|
|
||||||
|
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;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gl_gs_frame::set_current(draw_context_t ctx)
|
void gl_gs_frame::set_current(draw_context_t ctx)
|
||||||
{
|
{
|
||||||
if (!((QOpenGLContext*)ctx)->makeCurrent(this))
|
if (!ctx)
|
||||||
{
|
{
|
||||||
create();
|
fmt::throw_exception("Null context handle passed to set_current" HERE);
|
||||||
((QOpenGLContext*)ctx)->makeCurrent(this);
|
}
|
||||||
|
|
||||||
|
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)
|
void gl_gs_frame::delete_context(draw_context_t ctx)
|
||||||
{
|
{
|
||||||
|
|
||||||
auto gl_ctx = (QOpenGLContext*)ctx;
|
auto gl_ctx = (GLContext*)ctx;
|
||||||
gl_ctx->doneCurrent();
|
gl_ctx->handle->doneCurrent();
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
delete gl_ctx;
|
delete gl_ctx->handle;
|
||||||
#else
|
#else
|
||||||
//AMD driver crashes when executing wglDeleteContext
|
//AMD driver crashes when executing wglDeleteContext
|
||||||
//Catch with SEH
|
//Catch with SEH
|
||||||
__try
|
__try
|
||||||
{
|
{
|
||||||
delete gl_ctx;
|
delete gl_ctx->handle;
|
||||||
}
|
}
|
||||||
__except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
|
__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");
|
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
|
#endif
|
||||||
|
|
||||||
|
if (gl_ctx->owner)
|
||||||
|
{
|
||||||
|
delete gl_ctx->surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete gl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gl_gs_frame::flip(draw_context_t context, bool skip_frame)
|
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
|
//Do not swap buffers if frame skip is active
|
||||||
if (skip_frame) return;
|
if (skip_frame) return;
|
||||||
|
|
||||||
((QOpenGLContext*)context)->swapBuffers(this);
|
auto gl_ctx = (GLContext*)context;
|
||||||
|
gl_ctx->handle->swapBuffers(gl_ctx->surface);
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,18 @@
|
|||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "gs_frame.h"
|
#include "gs_frame.h"
|
||||||
|
|
||||||
|
struct GLContext
|
||||||
|
{
|
||||||
|
QSurface *surface = nullptr;
|
||||||
|
QOpenGLContext *handle = nullptr;
|
||||||
|
bool owner = false;
|
||||||
|
};
|
||||||
|
|
||||||
class gl_gs_frame : public gs_frame
|
class gl_gs_frame : public gs_frame
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
QSurfaceFormat m_format;
|
QSurfaceFormat m_format;
|
||||||
|
GLContext *m_primary_context = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse);
|
gl_gs_frame(const QRect& geometry, QIcon appIcon, bool disableMouse);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user