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