Browse Source
Merge pull request #1165 from bunnei/shader-cache
Merge pull request #1165 from bunnei/shader-cache
renderer_opengl: Implement a new shader cache.nce_cpp
committed by
GitHub
12 changed files with 387 additions and 417 deletions
-
3src/video_core/CMakeLists.txt
-
116src/video_core/rasterizer_cache.h
-
89src/video_core/renderer_opengl/gl_rasterizer.cpp
-
15src/video_core/renderer_opengl/gl_rasterizer.h
-
110src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
-
37src/video_core/renderer_opengl/gl_rasterizer_cache.h
-
131src/video_core/renderer_opengl/gl_shader_cache.cpp
-
69src/video_core/renderer_opengl/gl_shader_cache.h
-
4src/video_core/renderer_opengl/gl_shader_gen.cpp
-
75src/video_core/renderer_opengl/gl_shader_gen.h
-
29src/video_core/renderer_opengl/gl_shader_manager.cpp
-
126src/video_core/renderer_opengl/gl_shader_manager.h
@ -0,0 +1,116 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <unordered_map> |
||||
|
#include <boost/icl/interval_map.hpp> |
||||
|
#include <boost/range/iterator_range.hpp> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "core/memory.h" |
||||
|
#include "video_core/memory_manager.h" |
||||
|
|
||||
|
template <class T> |
||||
|
class RasterizerCache : NonCopyable { |
||||
|
public: |
||||
|
/// Mark the specified region as being invalidated |
||||
|
void InvalidateRegion(Tegra::GPUVAddr region_addr, size_t region_size) { |
||||
|
for (auto iter = cached_objects.cbegin(); iter != cached_objects.cend();) { |
||||
|
const auto& object{iter->second}; |
||||
|
|
||||
|
++iter; |
||||
|
|
||||
|
if (object->GetAddr() <= (region_addr + region_size) && |
||||
|
region_addr <= (object->GetAddr() + object->GetSizeInBytes())) { |
||||
|
// Regions overlap, so invalidate |
||||
|
Unregister(object); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
/// Tries to get an object from the cache with the specified address |
||||
|
T TryGet(Tegra::GPUVAddr addr) const { |
||||
|
const auto& search{cached_objects.find(addr)}; |
||||
|
if (search != cached_objects.end()) { |
||||
|
return search->second; |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
/// Gets a reference to the cache |
||||
|
const std::unordered_map<Tegra::GPUVAddr, T>& GetCache() const { |
||||
|
return cached_objects; |
||||
|
} |
||||
|
|
||||
|
/// Register an object into the cache |
||||
|
void Register(const T& object) { |
||||
|
const auto& search{cached_objects.find(object->GetAddr())}; |
||||
|
if (search != cached_objects.end()) { |
||||
|
// Registered already |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
cached_objects[object->GetAddr()] = object; |
||||
|
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1); |
||||
|
} |
||||
|
|
||||
|
/// Unregisters an object from the cache |
||||
|
void Unregister(const T& object) { |
||||
|
const auto& search{cached_objects.find(object->GetAddr())}; |
||||
|
if (search == cached_objects.end()) { |
||||
|
// Unregistered already |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1); |
||||
|
cached_objects.erase(search); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
using PageMap = boost::icl::interval_map<u64, int>; |
||||
|
|
||||
|
template <typename Map, typename Interval> |
||||
|
constexpr auto RangeFromInterval(Map& map, const Interval& interval) { |
||||
|
return boost::make_iterator_range(map.equal_range(interval)); |
||||
|
} |
||||
|
|
||||
|
/// Increase/decrease the number of object in pages touching the specified region |
||||
|
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) { |
||||
|
const u64 page_start{addr >> Tegra::MemoryManager::PAGE_BITS}; |
||||
|
const u64 page_end{(addr + size) >> Tegra::MemoryManager::PAGE_BITS}; |
||||
|
|
||||
|
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to |
||||
|
// subtract after iterating |
||||
|
const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end); |
||||
|
if (delta > 0) |
||||
|
cached_pages.add({pages_interval, delta}); |
||||
|
|
||||
|
for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) { |
||||
|
const auto interval = pair.first & pages_interval; |
||||
|
const int count = pair.second; |
||||
|
|
||||
|
const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval) |
||||
|
<< Tegra::MemoryManager::PAGE_BITS; |
||||
|
const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval) |
||||
|
<< Tegra::MemoryManager::PAGE_BITS; |
||||
|
const u64 interval_size = interval_end_addr - interval_start_addr; |
||||
|
|
||||
|
if (delta > 0 && count == delta) |
||||
|
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true); |
||||
|
else if (delta < 0 && count == -delta) |
||||
|
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false); |
||||
|
else |
||||
|
ASSERT(count >= 0); |
||||
|
} |
||||
|
|
||||
|
if (delta < 0) |
||||
|
cached_pages.add({pages_interval, delta}); |
||||
|
} |
||||
|
|
||||
|
std::unordered_map<Tegra::GPUVAddr, T> cached_objects; |
||||
|
PageMap cached_pages; |
||||
|
}; |
||||
@ -0,0 +1,131 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/assert.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/memory.h"
|
||||
|
#include "video_core/engines/maxwell_3d.h"
|
||||
|
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
|
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
|
|
||||
|
namespace OpenGL { |
||||
|
|
||||
|
/// Gets the address for the specified shader stage program
|
||||
|
static Tegra::GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) { |
||||
|
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); |
||||
|
|
||||
|
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH); |
||||
|
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)]; |
||||
|
return gpu.regs.code_address.CodeAddress() + shader_config.offset; |
||||
|
} |
||||
|
|
||||
|
/// Gets the shader program code from memory for the specified address
|
||||
|
static GLShader::ProgramCode GetShaderCode(Tegra::GPUVAddr addr) { |
||||
|
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); |
||||
|
|
||||
|
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH); |
||||
|
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(addr)}; |
||||
|
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64)); |
||||
|
|
||||
|
return program_code; |
||||
|
} |
||||
|
|
||||
|
/// Helper function to set shader uniform block bindings for a single shader stage
|
||||
|
static void SetShaderUniformBlockBinding(GLuint shader, const char* name, |
||||
|
Maxwell::ShaderStage binding, size_t expected_size) { |
||||
|
const GLuint ub_index = glGetUniformBlockIndex(shader, name); |
||||
|
if (ub_index == GL_INVALID_INDEX) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
GLint ub_size = 0; |
||||
|
glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); |
||||
|
ASSERT_MSG(static_cast<size_t>(ub_size) == expected_size, |
||||
|
"Uniform block size did not match! Got {}, expected {}", ub_size, expected_size); |
||||
|
glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding)); |
||||
|
} |
||||
|
|
||||
|
/// Sets shader uniform block bindings for an entire shader program
|
||||
|
static void SetShaderUniformBlockBindings(GLuint shader) { |
||||
|
SetShaderUniformBlockBinding(shader, "vs_config", Maxwell::ShaderStage::Vertex, |
||||
|
sizeof(GLShader::MaxwellUniformData)); |
||||
|
SetShaderUniformBlockBinding(shader, "gs_config", Maxwell::ShaderStage::Geometry, |
||||
|
sizeof(GLShader::MaxwellUniformData)); |
||||
|
SetShaderUniformBlockBinding(shader, "fs_config", Maxwell::ShaderStage::Fragment, |
||||
|
sizeof(GLShader::MaxwellUniformData)); |
||||
|
} |
||||
|
|
||||
|
CachedShader::CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type) |
||||
|
: addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} { |
||||
|
|
||||
|
GLShader::ProgramResult program_result; |
||||
|
GLenum gl_type{}; |
||||
|
|
||||
|
switch (program_type) { |
||||
|
case Maxwell::ShaderProgram::VertexA: |
||||
|
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
|
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
|
// stage here.
|
||||
|
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB))); |
||||
|
case Maxwell::ShaderProgram::VertexB: |
||||
|
program_result = GLShader::GenerateVertexShader(setup); |
||||
|
gl_type = GL_VERTEX_SHADER; |
||||
|
break; |
||||
|
case Maxwell::ShaderProgram::Fragment: |
||||
|
program_result = GLShader::GenerateFragmentShader(setup); |
||||
|
gl_type = GL_FRAGMENT_SHADER; |
||||
|
break; |
||||
|
default: |
||||
|
LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type)); |
||||
|
UNREACHABLE(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
entries = program_result.second; |
||||
|
|
||||
|
OGLShader shader; |
||||
|
shader.Create(program_result.first.c_str(), gl_type); |
||||
|
program.Create(true, shader.handle); |
||||
|
SetShaderUniformBlockBindings(program.handle); |
||||
|
} |
||||
|
|
||||
|
GLuint CachedShader::GetProgramResourceIndex(const std::string& name) { |
||||
|
auto search{resource_cache.find(name)}; |
||||
|
if (search == resource_cache.end()) { |
||||
|
const GLuint index{ |
||||
|
glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, name.c_str())}; |
||||
|
resource_cache[name] = index; |
||||
|
return index; |
||||
|
} |
||||
|
|
||||
|
return search->second; |
||||
|
} |
||||
|
|
||||
|
GLint CachedShader::GetUniformLocation(const std::string& name) { |
||||
|
auto search{uniform_cache.find(name)}; |
||||
|
if (search == uniform_cache.end()) { |
||||
|
const GLint index{glGetUniformLocation(program.handle, name.c_str())}; |
||||
|
uniform_cache[name] = index; |
||||
|
return index; |
||||
|
} |
||||
|
|
||||
|
return search->second; |
||||
|
} |
||||
|
|
||||
|
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { |
||||
|
const Tegra::GPUVAddr program_addr{GetShaderAddress(program)}; |
||||
|
|
||||
|
// Look up shader in the cache based on address
|
||||
|
Shader shader{TryGet(program_addr)}; |
||||
|
|
||||
|
if (!shader) { |
||||
|
// No shader found - create a new one
|
||||
|
shader = std::make_shared<CachedShader>(program_addr, program); |
||||
|
Register(shader); |
||||
|
} |
||||
|
|
||||
|
return shader; |
||||
|
} |
||||
|
|
||||
|
} // namespace OpenGL
|
||||
@ -0,0 +1,69 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "video_core/memory_manager.h" |
||||
|
#include "video_core/rasterizer_cache.h" |
||||
|
#include "video_core/renderer_opengl/gl_resource_manager.h" |
||||
|
#include "video_core/renderer_opengl/gl_shader_gen.h" |
||||
|
|
||||
|
namespace OpenGL { |
||||
|
|
||||
|
class CachedShader; |
||||
|
using Shader = std::shared_ptr<CachedShader>; |
||||
|
using Maxwell = Tegra::Engines::Maxwell3D::Regs; |
||||
|
|
||||
|
class CachedShader final { |
||||
|
public: |
||||
|
CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type); |
||||
|
|
||||
|
/// Gets the address of the shader in guest memory, required for cache management |
||||
|
Tegra::GPUVAddr GetAddr() const { |
||||
|
return addr; |
||||
|
} |
||||
|
|
||||
|
/// Gets the size of the shader in guest memory, required for cache management |
||||
|
size_t GetSizeInBytes() const { |
||||
|
return sizeof(GLShader::ProgramCode); |
||||
|
} |
||||
|
|
||||
|
/// Gets the shader entries for the shader |
||||
|
const GLShader::ShaderEntries& GetShaderEntries() const { |
||||
|
return entries; |
||||
|
} |
||||
|
|
||||
|
/// Gets the GL program handle for the shader |
||||
|
GLuint GetProgramHandle() const { |
||||
|
return program.handle; |
||||
|
} |
||||
|
|
||||
|
/// Gets the GL program resource location for the specified resource, caching as needed |
||||
|
GLuint GetProgramResourceIndex(const std::string& name); |
||||
|
|
||||
|
/// Gets the GL uniform location for the specified resource, caching as needed |
||||
|
GLint GetUniformLocation(const std::string& name); |
||||
|
|
||||
|
private: |
||||
|
Tegra::GPUVAddr addr; |
||||
|
Maxwell::ShaderProgram program_type; |
||||
|
GLShader::ShaderSetup setup; |
||||
|
GLShader::ShaderEntries entries; |
||||
|
OGLProgram program; |
||||
|
|
||||
|
std::unordered_map<std::string, GLuint> resource_cache; |
||||
|
std::unordered_map<std::string, GLint> uniform_cache; |
||||
|
}; |
||||
|
|
||||
|
class ShaderCacheOpenGL final : public RasterizerCache<Shader> { |
||||
|
public: |
||||
|
/// Gets the current specified shader stage program |
||||
|
Shader GetStageProgram(Maxwell::ShaderProgram program); |
||||
|
}; |
||||
|
|
||||
|
} // namespace OpenGL |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue