mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Rasterizer cache refactor v2 (#6479)
* rasterizer_cache: Switch to template * Eliminates all opengl references in the rasterizer cache headers thus completing the backend abstraction * rasterizer_cache: Switch to page table * Surface storage isn't particularly interval sensitive so we can use a page table to make it faster * rasterizer_cache: Move sampler management out of rasterizer cache * rasterizer_cache: Remove shared_ptr usage * Switches to yuzu's slot vector for improved memory locality. * rasterizer_cache: Rework reinterpretation lookup * citra_qt: Per game texture filter * rasterizer_cache: Log additional settings * gl_texture_runtime: Resolve shadow map comment * rasterizer_cache: Don't use float for viewport * gl_texture_runtime: Fix custom allocation recycling * rasterizer_cache: Minor cleanups * Cleanup texture cubes when all the faces have been unregistered from the cache * custom_tex_manager: Allow multiple hash mappings per texture * code: Move slot vector to common * rasterizer_cache: Prevent texture cube crashes * rasterizer_cache: Improve mipmap validation * CanSubRect now works properly when validating multi-level surfaces, for example Dark Moon validates a 4 level surface from a 3 level one and it works * gl_blit_handler: Unbind sampler on reinterpretation
This commit is contained in:
		
							parent
							
								
									322d7a8287
								
							
						
					
					
						commit
						2e655f73b8
					
				
					 32 changed files with 2238 additions and 1927 deletions
				
			
		|  | @ -40,6 +40,8 @@ add_library(video_core STATIC | |||
|     rasterizer_cache/pixel_format.h | ||||
|     rasterizer_cache/rasterizer_cache.cpp | ||||
|     rasterizer_cache/rasterizer_cache.h | ||||
|     rasterizer_cache/rasterizer_cache_base.h | ||||
|     rasterizer_cache/sampler_params.h | ||||
|     rasterizer_cache/surface_base.cpp | ||||
|     rasterizer_cache/surface_base.h | ||||
|     rasterizer_cache/surface_params.cpp | ||||
|  | @ -53,10 +55,9 @@ add_library(video_core STATIC | |||
|     renderer_opengl/gl_blit_helper.h | ||||
|     renderer_opengl/gl_driver.cpp | ||||
|     renderer_opengl/gl_driver.h | ||||
|     renderer_opengl/gl_format_reinterpreter.cpp | ||||
|     renderer_opengl/gl_format_reinterpreter.h | ||||
|     renderer_opengl/gl_rasterizer.cpp | ||||
|     renderer_opengl/gl_rasterizer.h | ||||
|     renderer_opengl/gl_rasterizer_cache.cpp | ||||
|     renderer_opengl/gl_resource_manager.cpp | ||||
|     renderer_opengl/gl_resource_manager.h | ||||
|     renderer_opengl/gl_shader_decompiler.cpp | ||||
|  | @ -126,7 +127,7 @@ target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) | |||
| create_target_directory_groups(video_core) | ||||
| 
 | ||||
| target_link_libraries(video_core PUBLIC citra_common citra_core) | ||||
| target_link_libraries(video_core PRIVATE glad json-headers dds-ktx nihstro-headers Boost::serialization) | ||||
| target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx glad json-headers nihstro-headers tsl::robin_map) | ||||
| set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) | ||||
| 
 | ||||
| if ("x86_64" IN_LIST ARCHITECTURE) | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "core/frontend/image_interface.h" | ||||
| #include "video_core/custom_textures/custom_tex_manager.h" | ||||
| #include "video_core/rasterizer_cache/surface_params.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
|  | @ -21,7 +22,7 @@ namespace { | |||
| MICROPROFILE_DEFINE(CustomTexManager_TickFrame, "CustomTexManager", "TickFrame", | ||||
|                     MP_RGB(54, 16, 32)); | ||||
| 
 | ||||
| constexpr std::size_t MAX_UPLOADS_PER_TICK = 16; | ||||
| constexpr std::size_t MAX_UPLOADS_PER_TICK = 8; | ||||
| 
 | ||||
| bool IsPow2(u32 value) { | ||||
|     return value != 0 && (value & (value - 1)) == 0; | ||||
|  | @ -111,11 +112,14 @@ void CustomTexManager::FindCustomTextures() { | |||
|         if (!ParseFilename(file, texture)) { | ||||
|             continue; | ||||
|         } | ||||
|         auto& material = material_map[texture->hash]; | ||||
|         if (!material) { | ||||
|             material = std::make_unique<Material>(); | ||||
|         for (const u64 hash : texture->hashes) { | ||||
|             auto& material = material_map[hash]; | ||||
|             if (!material) { | ||||
|                 material = std::make_unique<Material>(); | ||||
|             } | ||||
|             material->hash = hash; | ||||
|             material->AddMapTexture(texture); | ||||
|         } | ||||
|         material->AddMapTexture(texture); | ||||
|     } | ||||
|     textures_loaded = true; | ||||
| } | ||||
|  | @ -145,21 +149,25 @@ bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTextu | |||
|         parts.pop_back(); | ||||
|     } | ||||
| 
 | ||||
|     // First check if the path is mapped directly to a hash
 | ||||
|     // before trying to parse the texture filename.
 | ||||
|     // First look if this file is mapped to any number of hashes.
 | ||||
|     std::vector<u64>& hashes = texture->hashes; | ||||
|     const auto it = path_to_hash_map.find(file.virtualName); | ||||
|     if (it != path_to_hash_map.end()) { | ||||
|         texture->hash = it->second; | ||||
|     } else { | ||||
|         u32 width; | ||||
|         u32 height; | ||||
|         u32 format; | ||||
|         unsigned long long hash{}; | ||||
|         if (std::sscanf(parts.back().c_str(), "tex1_%ux%u_%llX_%u", &width, &height, &hash, | ||||
|                         &format) != 4) { | ||||
|             return false; | ||||
|         } | ||||
|         texture->hash = hash; | ||||
|         hashes = it->second; | ||||
|     } | ||||
| 
 | ||||
|     // It's also possible for pack creators to retain the default texture name
 | ||||
|     // still map the texture to another hash. Support that as well.
 | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|     u32 format; | ||||
|     unsigned long long hash{}; | ||||
|     const bool is_parsed = std::sscanf(parts.back().c_str(), "tex1_%ux%u_%llX_%u", &width, &height, | ||||
|                                        &hash, &format) == 4; | ||||
|     const bool is_mapped = | ||||
|         !hashes.empty() && std::find(hashes.begin(), hashes.end(), hash) != hashes.end(); | ||||
|     if (is_parsed && !is_mapped) { | ||||
|         hashes.push_back(hash); | ||||
|     } | ||||
| 
 | ||||
|     texture->path = file.physicalName; | ||||
|  | @ -181,9 +189,9 @@ void CustomTexManager::WriteConfig() { | |||
|     json["description"] = "A graphics pack"; | ||||
| 
 | ||||
|     auto& options = json["options"]; | ||||
|     options["skip_mipmap"] = skip_mipmap; | ||||
|     options["flip_png_files"] = flip_png_files; | ||||
|     options["use_new_hash"] = use_new_hash; | ||||
|     options["skip_mipmap"] = false; | ||||
|     options["flip_png_files"] = true; | ||||
|     options["use_new_hash"] = true; | ||||
| 
 | ||||
|     FileUtil::IOFile file{pack_config, "w"}; | ||||
|     const std::string output = json.dump(4); | ||||
|  | @ -311,7 +319,7 @@ void CustomTexManager::ReadConfig(const std::string& load_path) { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     nlohmann::json json = nlohmann::json::parse(config); | ||||
|     nlohmann::json json = nlohmann::json::parse(config, nullptr, false, true); | ||||
| 
 | ||||
|     const auto& options = json["options"]; | ||||
|     skip_mipmap = options["skip_mipmap"].get<bool>(); | ||||
|  | @ -330,13 +338,7 @@ void CustomTexManager::ReadConfig(const std::string& load_path) { | |||
|         const auto parse = [&](const std::string& file) { | ||||
|             const std::string filename{FileUtil::GetFilename(file)}; | ||||
|             auto [it, new_hash] = path_to_hash_map.try_emplace(filename); | ||||
|             if (!new_hash) { | ||||
|                 LOG_ERROR(Render, | ||||
|                           "File {} with key {} already exists and is mapped to {:#016X}, skipping", | ||||
|                           file, material.key(), path_to_hash_map[filename]); | ||||
|                 return; | ||||
|             } | ||||
|             it->second = hash; | ||||
|             it->second.push_back(hash); | ||||
|         }; | ||||
|         const auto value = material.value(); | ||||
|         if (value.is_string()) { | ||||
|  |  | |||
|  | @ -81,7 +81,7 @@ private: | |||
|     Frontend::ImageInterface& image_interface; | ||||
|     std::unordered_set<u64> dumped_textures; | ||||
|     std::unordered_map<u64, std::unique_ptr<Material>> material_map; | ||||
|     std::unordered_map<std::string, u64> path_to_hash_map; | ||||
|     std::unordered_map<std::string, std::vector<u64>> path_to_hash_map; | ||||
|     std::vector<std::unique_ptr<CustomTexture>> custom_textures; | ||||
|     std::list<AsyncUpload> async_uploads; | ||||
|     std::unique_ptr<Common::ThreadWorker> workers; | ||||
|  |  | |||
|  | @ -55,6 +55,11 @@ CustomTexture::CustomTexture(Frontend::ImageInterface& image_interface_) | |||
| CustomTexture::~CustomTexture() = default; | ||||
| 
 | ||||
| void CustomTexture::LoadFromDisk(bool flip_png) { | ||||
|     std::scoped_lock lock{decode_mutex}; | ||||
|     if (IsLoaded()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     FileUtil::IOFile file{path, "rb"}; | ||||
|     std::vector<u8> input(file.GetSize()); | ||||
|     if (file.ReadBytes(input.data(), input.size()) != input.size()) { | ||||
|  | @ -71,7 +76,6 @@ void CustomTexture::LoadFromDisk(bool flip_png) { | |||
|         break; | ||||
|     default: | ||||
|         LOG_ERROR(Render, "Unknown file format {}", file_format); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -102,8 +106,7 @@ void Material::LoadFromDisk(bool flip_png) noexcept { | |||
|         } | ||||
|         texture->LoadFromDisk(flip_png); | ||||
|         size += texture->data.size(); | ||||
|         LOG_DEBUG(Render, "Loading {} map {} with hash {:#016X}", MapTypeName(texture->type), | ||||
|                   texture->path, texture->hash); | ||||
|         LOG_DEBUG(Render, "Loading {} map {}", MapTypeName(texture->type), texture->path); | ||||
|     } | ||||
|     if (!textures[0]) { | ||||
|         LOG_ERROR(Render, "Unable to create material without color texture!"); | ||||
|  | @ -121,7 +124,7 @@ void Material::LoadFromDisk(bool flip_png) noexcept { | |||
|             LOG_ERROR(Render, | ||||
|                       "{} map {} of material with hash {:#016X} has dimentions {}x{} " | ||||
|                       "which do not match the color texture dimentions {}x{}", | ||||
|                       MapTypeName(texture->type), texture->path, texture->hash, texture->width, | ||||
|                       MapTypeName(texture->type), texture->path, hash, texture->width, | ||||
|                       texture->height, width, height); | ||||
|             state = DecodeState::Failed; | ||||
|             return; | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <mutex> | ||||
| #include <span> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | @ -39,7 +40,7 @@ public: | |||
|     void LoadFromDisk(bool flip_png); | ||||
| 
 | ||||
|     [[nodiscard]] bool IsParsed() const noexcept { | ||||
|         return file_format != CustomFileFormat::None && hash != 0; | ||||
|         return file_format != CustomFileFormat::None && !hashes.empty(); | ||||
|     } | ||||
| 
 | ||||
|     [[nodiscard]] bool IsLoaded() const noexcept { | ||||
|  | @ -56,7 +57,8 @@ public: | |||
|     std::string path; | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|     u64 hash; | ||||
|     std::vector<u64> hashes; | ||||
|     std::mutex decode_mutex; | ||||
|     CustomPixelFormat format; | ||||
|     CustomFileFormat file_format; | ||||
|     std::vector<u8> data; | ||||
|  | @ -67,6 +69,7 @@ struct Material { | |||
|     u32 width; | ||||
|     u32 height; | ||||
|     u64 size; | ||||
|     u64 hash; | ||||
|     CustomPixelFormat format; | ||||
|     std::array<CustomTexture*, MAX_MAPS> textures; | ||||
|     std::atomic<DecodeState> state{}; | ||||
|  |  | |||
|  | @ -4,29 +4,18 @@ | |||
| 
 | ||||
| //? #version 430 core | ||||
| 
 | ||||
| layout(location = 0) in mediump vec2 dst_coord; | ||||
| layout(location = 0) in mediump vec2 tex_coord; | ||||
| layout(location = 0) out lowp vec4 frag_color; | ||||
| 
 | ||||
| layout(binding = 0) uniform highp sampler2D depth; | ||||
| layout(binding = 1) uniform lowp usampler2D stencil; | ||||
| uniform mediump ivec2 dst_size; | ||||
| uniform mediump ivec2 src_size; | ||||
| uniform mediump ivec2 src_offset; | ||||
| 
 | ||||
| void main() { | ||||
|     mediump ivec2 tex_coord; | ||||
|     if (src_size == dst_size) { | ||||
|         tex_coord = ivec2(dst_coord); | ||||
|     } else { | ||||
|         highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x); | ||||
|         mediump int y = tex_index / src_size.x; | ||||
|         tex_coord = ivec2(tex_index - y * src_size.x, y); | ||||
|     } | ||||
|     tex_coord -= src_offset; | ||||
| 
 | ||||
|     mediump vec2 coord = tex_coord * vec2(textureSize(depth, 0)); | ||||
|     mediump ivec2 tex_icoord = ivec2(coord); | ||||
|     highp uint depth_val = | ||||
|         uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0)); | ||||
|     lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x; | ||||
|         uint(texelFetch(depth, tex_icoord, 0).x * (exp2(32.0) - 1.0)); | ||||
|     lowp uint stencil_val = texelFetch(stencil, tex_icoord, 0).x; | ||||
|     highp uvec4 components = | ||||
|         uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu); | ||||
|     frag_color = vec4(components) / (exp2(8.0) - 1.0); | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
| 
 | ||||
| layout(location = 0) out vec2 dst_coord; | ||||
| 
 | ||||
| uniform mediump ivec2 dst_size; | ||||
| layout(location = 0) uniform mediump ivec2 dst_size; | ||||
| 
 | ||||
| const vec2 vertices[4] = | ||||
| vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)); | ||||
|  |  | |||
|  | @ -4,26 +4,15 @@ | |||
| 
 | ||||
| //? #version 430 core | ||||
| 
 | ||||
| layout(location = 0) in mediump vec2 dst_coord; | ||||
| layout(location = 0) in mediump vec2 tex_coord; | ||||
| layout(location = 0) out lowp vec4 frag_color; | ||||
| 
 | ||||
| layout(binding = 0) uniform lowp sampler2D source; | ||||
| uniform mediump ivec2 dst_size; | ||||
| uniform mediump ivec2 src_size; | ||||
| uniform mediump ivec2 src_offset; | ||||
| 
 | ||||
| void main() { | ||||
|     mediump ivec2 tex_coord; | ||||
|     if (src_size == dst_size) { | ||||
|         tex_coord = ivec2(dst_coord); | ||||
|     } else { | ||||
|         highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x); | ||||
|         mediump int y = tex_index / src_size.x; | ||||
|         tex_coord = ivec2(tex_index - y * src_size.x, y); | ||||
|     } | ||||
|     tex_coord -= src_offset; | ||||
| 
 | ||||
|     lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_coord, 0) * (exp2(4.0) - 1.0)); | ||||
|     mediump vec2 coord = tex_coord * vec2(textureSize(source, 0)); | ||||
|     mediump ivec2 tex_icoord = ivec2(coord); | ||||
|     lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_icoord, 0) * (exp2(4.0) - 1.0)); | ||||
|     lowp ivec3 rgb5 = | ||||
|         ((rgba4.rgb << ivec3(1, 2, 3)) | (rgba4.gba >> ivec3(3, 2, 1))) & 0x1F; | ||||
|     frag_color = vec4(vec3(rgb5) / (exp2(5.0) - 1.0), rgba4.a & 0x01); | ||||
|  |  | |||
|  | @ -10,9 +10,9 @@ namespace VideoCore { | |||
| 
 | ||||
| FramebufferBase::FramebufferBase() = default; | ||||
| 
 | ||||
| FramebufferBase::FramebufferBase(const Pica::Regs& regs, const SurfaceBase* const color, | ||||
|                                  u32 color_level, const SurfaceBase* const depth_stencil, | ||||
|                                  u32 depth_level, Common::Rectangle<u32> surfaces_rect) { | ||||
| FramebufferBase::FramebufferBase(const Pica::Regs& regs, const SurfaceBase* color, u32 color_level, | ||||
|                                  const SurfaceBase* depth_stencil, u32 depth_level, | ||||
|                                  Common::Rectangle<u32> surfaces_rect) { | ||||
|     res_scale = color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u); | ||||
| 
 | ||||
|     // Determine the draw rectangle (render area + scissor)
 | ||||
|  | @ -31,10 +31,10 @@ FramebufferBase::FramebufferBase(const Pica::Regs& regs, const SurfaceBase* cons | |||
|                         surfaces_rect.bottom, surfaces_rect.top); | ||||
| 
 | ||||
|     // Update viewport
 | ||||
|     viewport.x = static_cast<f32>(surfaces_rect.left + viewport_rect.left * res_scale); | ||||
|     viewport.y = static_cast<f32>(surfaces_rect.bottom + viewport_rect.bottom * res_scale); | ||||
|     viewport.width = static_cast<f32>(viewport_rect.GetWidth() * res_scale); | ||||
|     viewport.height = static_cast<f32>(viewport_rect.GetHeight() * res_scale); | ||||
|     viewport.x = static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale; | ||||
|     viewport.y = static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale; | ||||
|     viewport.width = static_cast<s32>(viewport_rect.GetWidth() * res_scale); | ||||
|     viewport.height = static_cast<s32>(viewport_rect.GetHeight() * res_scale); | ||||
| 
 | ||||
|     // Scissor checks are window-, not viewport-relative, which means that if the cached texture
 | ||||
|     // sub-rect changes, the scissor bounds also need to be updated.
 | ||||
|  |  | |||
|  | @ -16,10 +16,10 @@ namespace VideoCore { | |||
| class SurfaceBase; | ||||
| 
 | ||||
| struct ViewportInfo { | ||||
|     f32 x; | ||||
|     f32 y; | ||||
|     f32 width; | ||||
|     f32 height; | ||||
|     s32 x; | ||||
|     s32 y; | ||||
|     s32 width; | ||||
|     s32 height; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -29,8 +29,8 @@ struct ViewportInfo { | |||
| class FramebufferBase { | ||||
| public: | ||||
|     FramebufferBase(); | ||||
|     FramebufferBase(const Pica::Regs& regs, const SurfaceBase* const color, u32 color_level, | ||||
|                     const SurfaceBase* const depth_stencil, u32 depth_level, | ||||
|     FramebufferBase(const Pica::Regs& regs, const SurfaceBase* color, u32 color_level, | ||||
|                     const SurfaceBase* depth_stencil, u32 depth_level, | ||||
|                     Common::Rectangle<u32> surfaces_rect); | ||||
| 
 | ||||
|     SurfaceParams ColorParams() const noexcept { | ||||
|  | @ -66,6 +66,7 @@ protected: | |||
|         switch (type) { | ||||
|         case VideoCore::SurfaceType::Color: | ||||
|             return 0; | ||||
|         case VideoCore::SurfaceType::Depth: | ||||
|         case VideoCore::SurfaceType::DepthStencil: | ||||
|             return 1; | ||||
|         default: | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										229
									
								
								src/video_core/rasterizer_cache/rasterizer_cache_base.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								src/video_core/rasterizer_cache/rasterizer_cache_base.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,229 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <optional> | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| #include <boost/icl/interval_map.hpp> | ||||
| #include <tsl/robin_map.h> | ||||
| #include "video_core/rasterizer_cache/sampler_params.h" | ||||
| #include "video_core/rasterizer_cache/surface_base.h" | ||||
| 
 | ||||
| namespace Memory { | ||||
| class MemorySystem; | ||||
| } | ||||
| 
 | ||||
| namespace Pica { | ||||
| struct Regs; | ||||
| } | ||||
| 
 | ||||
| namespace Pica::Texture { | ||||
| struct TextureInfo; | ||||
| } | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
| enum class ScaleMatch { | ||||
|     Exact,   ///< Only accept same res scale
 | ||||
|     Upscale, ///< Only allow higher scale than params
 | ||||
|     Ignore   ///< Accept every scaled res
 | ||||
| }; | ||||
| 
 | ||||
| enum class MatchFlags { | ||||
|     Exact = 1 << 0,       ///< Surface perfectly matches params
 | ||||
|     SubRect = 1 << 1,     ///< Surface encompasses params
 | ||||
|     Copy = 1 << 2,        ///< Surface that can be used as a copy source
 | ||||
|     Expand = 1 << 3,      ///< Surface that can expand params
 | ||||
|     TexCopy = 1 << 4,     ///< Surface that will match a display transfer "texture copy" parameters
 | ||||
|     Reinterpret = 1 << 5, ///< Surface might have different pixel format.
 | ||||
| }; | ||||
| 
 | ||||
| DECLARE_ENUM_FLAG_OPERATORS(MatchFlags); | ||||
| 
 | ||||
| class CustomTexManager; | ||||
| class RendererBase; | ||||
| 
 | ||||
| template <class T> | ||||
| class RasterizerCache { | ||||
|     /// Address shift for caching surfaces into a hash table
 | ||||
|     static constexpr u64 CITRA_PAGEBITS = 18; | ||||
| 
 | ||||
|     using Runtime = typename T::Runtime; | ||||
|     using Sampler = typename T::Sampler; | ||||
|     using Surface = typename T::Surface; | ||||
|     using Framebuffer = typename T::Framebuffer; | ||||
| 
 | ||||
|     using SurfaceMap = boost::icl::interval_map<PAddr, SurfaceId, boost::icl::partial_absorber, | ||||
|                                                 std::less, boost::icl::inplace_plus, | ||||
|                                                 boost::icl::inter_section, SurfaceInterval>; | ||||
| 
 | ||||
|     using SurfaceRect_Tuple = std::pair<SurfaceId, Common::Rectangle<u32>>; | ||||
|     using PageMap = boost::icl::interval_map<u32, int>; | ||||
| 
 | ||||
|     struct RenderTargets { | ||||
|         SurfaceId color_id; | ||||
|         SurfaceId depth_id; | ||||
|     }; | ||||
| 
 | ||||
|     struct TextureCube { | ||||
|         SurfaceId surface_id; | ||||
|         std::array<SurfaceId, 6> face_ids; | ||||
|         std::array<u64, 6> ticks; | ||||
|     }; | ||||
| 
 | ||||
| public: | ||||
|     explicit RasterizerCache(Memory::MemorySystem& memory, CustomTexManager& custom_tex_manager, | ||||
|                              Runtime& runtime, Pica::Regs& regs, RendererBase& renderer); | ||||
|     ~RasterizerCache(); | ||||
| 
 | ||||
|     /// Notify the cache that a new frame has been queued
 | ||||
|     void TickFrame(); | ||||
| 
 | ||||
|     /// Perform hardware accelerated texture copy according to the provided configuration
 | ||||
|     bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config); | ||||
| 
 | ||||
|     /// Perform hardware accelerated display transfer according to the provided configuration
 | ||||
|     bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config); | ||||
| 
 | ||||
|     /// Perform hardware accelerated memory fill according to the provided configuration
 | ||||
|     bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config); | ||||
| 
 | ||||
|     /// Returns a reference to the surface object assigned to surface_id
 | ||||
|     Surface& GetSurface(SurfaceId surface_id); | ||||
| 
 | ||||
|     /// Returns a reference to the sampler object matching the provided configuration
 | ||||
|     Sampler& GetSampler(const Pica::TexturingRegs::TextureConfig& config); | ||||
|     Sampler& GetSampler(SamplerId sampler_id); | ||||
| 
 | ||||
|     /// Copy one surface's region to another
 | ||||
|     void CopySurface(Surface& src_surface, Surface& dst_surface, SurfaceInterval copy_interval); | ||||
| 
 | ||||
|     /// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
 | ||||
|     SurfaceId GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, | ||||
|                          bool load_if_create); | ||||
| 
 | ||||
|     /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
 | ||||
|     /// 3DS memory to OpenGL and caches it (if not already cached)
 | ||||
|     SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale, | ||||
|                                         bool load_if_create); | ||||
| 
 | ||||
|     /// Get a surface based on the texture configuration
 | ||||
|     Surface& GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config); | ||||
|     SurfaceId GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0); | ||||
| 
 | ||||
|     /// Get a texture cube based on the texture configuration
 | ||||
|     Surface& GetTextureCube(const TextureCubeConfig& config); | ||||
| 
 | ||||
|     /// Get the color and depth surfaces based on the framebuffer configuration
 | ||||
|     Framebuffer GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb); | ||||
| 
 | ||||
|     /// Marks the draw rectangle defined in framebuffer as invalid
 | ||||
|     void InvalidateFramebuffer(const Framebuffer& framebuffer); | ||||
| 
 | ||||
|     /// Get a surface that matches a "texture copy" display transfer config
 | ||||
|     SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); | ||||
| 
 | ||||
|     /// Write any cached resources overlapping the region back to memory (if dirty)
 | ||||
|     void FlushRegion(PAddr addr, u32 size, SurfaceId flush_surface = {}); | ||||
| 
 | ||||
|     /// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
 | ||||
|     void InvalidateRegion(PAddr addr, u32 size, SurfaceId region_owner = {}); | ||||
| 
 | ||||
|     /// Flush all cached resources tracked by this cache manager
 | ||||
|     void FlushAll(); | ||||
| 
 | ||||
|     /// Clear all cached resources tracked by this cache manager
 | ||||
|     void ClearAll(bool flush); | ||||
| 
 | ||||
| private: | ||||
|     /// Iterate over all page indices in a range
 | ||||
|     template <typename Func> | ||||
|     void ForEachPage(PAddr addr, size_t size, Func&& func) { | ||||
|         static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; | ||||
|         const u64 page_end = (addr + size - 1) >> CITRA_PAGEBITS; | ||||
|         for (u64 page = addr >> CITRA_PAGEBITS; page <= page_end; ++page) { | ||||
|             if constexpr (RETURNS_BOOL) { | ||||
|                 if (func(page)) { | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|                 func(page); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Iterates over all the surfaces in a region calling func
 | ||||
|     template <typename Func> | ||||
|     void ForEachSurfaceInRegion(PAddr addr, size_t size, Func&& func); | ||||
| 
 | ||||
|     /// Get the best surface match (and its match type) for the given flags
 | ||||
|     template <MatchFlags find_flags> | ||||
|     SurfaceId FindMatch(const SurfaceParams& params, ScaleMatch match_scale_type, | ||||
|                         std::optional<SurfaceInterval> validate_interval = std::nullopt); | ||||
| 
 | ||||
|     /// Transfers ownership of a memory region from src_surface to dest_surface
 | ||||
|     void DuplicateSurface(SurfaceId src_id, SurfaceId dst_id); | ||||
| 
 | ||||
|     /// Update surface's texture for given region when necessary
 | ||||
|     void ValidateSurface(SurfaceId surface, PAddr addr, u32 size); | ||||
| 
 | ||||
|     /// Copies pixel data in interval from the guest VRAM to the host GPU surface
 | ||||
|     void UploadSurface(Surface& surface, SurfaceInterval interval); | ||||
| 
 | ||||
|     /// Uploads a custom texture identified with hash to the target surface
 | ||||
|     bool UploadCustomSurface(SurfaceId surface_id, SurfaceInterval interval); | ||||
| 
 | ||||
|     /// Copies pixel data in interval from the host GPU surface to the guest VRAM
 | ||||
|     void DownloadSurface(Surface& surface, SurfaceInterval interval); | ||||
| 
 | ||||
|     /// Downloads a fill surface to guest VRAM
 | ||||
|     void DownloadFillSurface(Surface& surface, SurfaceInterval interval); | ||||
| 
 | ||||
|     /// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
 | ||||
|     bool ValidateByReinterpretation(Surface& surface, SurfaceParams params, | ||||
|                                     const SurfaceInterval& interval); | ||||
| 
 | ||||
|     /// Return true if a surface with an invalid pixel format exists at the interval
 | ||||
|     bool IntervalHasInvalidPixelFormat(const SurfaceParams& params, SurfaceInterval interval); | ||||
| 
 | ||||
|     /// Create a new surface
 | ||||
|     SurfaceId CreateSurface(const SurfaceParams& params); | ||||
| 
 | ||||
|     /// Register surface into the cache
 | ||||
|     void RegisterSurface(SurfaceId surface); | ||||
| 
 | ||||
|     /// Remove surface from the cache
 | ||||
|     void UnregisterSurface(SurfaceId surface); | ||||
| 
 | ||||
|     /// Unregisters all surfaces from the cache
 | ||||
|     void UnregisterAll(); | ||||
| 
 | ||||
|     /// Increase/decrease the number of surface in pages touching the specified region
 | ||||
|     void UpdatePagesCachedCount(PAddr addr, u32 size, int delta); | ||||
| 
 | ||||
| private: | ||||
|     Memory::MemorySystem& memory; | ||||
|     CustomTexManager& custom_tex_manager; | ||||
|     Runtime& runtime; | ||||
|     Pica::Regs& regs; | ||||
|     RendererBase& renderer; | ||||
|     std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache; | ||||
|     tsl::robin_pg_map<u64, std::vector<SurfaceId>, Common::IdentityHash<u64>> page_table; | ||||
|     std::unordered_map<SamplerParams, SamplerId> samplers; | ||||
|     Common::SlotVector<Surface> slot_surfaces; | ||||
|     Common::SlotVector<Sampler> slot_samplers; | ||||
|     SurfaceMap dirty_regions; | ||||
|     PageMap cached_pages; | ||||
|     std::vector<SurfaceId> remove_surfaces; | ||||
|     u32 resolution_scale_factor; | ||||
|     RenderTargets render_targets; | ||||
|     bool use_filter; | ||||
|     bool dump_textures; | ||||
|     bool use_custom_textures; | ||||
| }; | ||||
| 
 | ||||
| } // namespace VideoCore
 | ||||
							
								
								
									
										43
									
								
								src/video_core/rasterizer_cache/sampler_params.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/video_core/rasterizer_cache/sampler_params.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <compare> | ||||
| #include "common/hash.h" | ||||
| #include "video_core/regs_texturing.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
| struct SamplerParams { | ||||
|     using TextureConfig = Pica::TexturingRegs::TextureConfig; | ||||
|     TextureConfig::TextureFilter mag_filter; | ||||
|     TextureConfig::TextureFilter min_filter; | ||||
|     TextureConfig::TextureFilter mip_filter; | ||||
|     TextureConfig::WrapMode wrap_s; | ||||
|     TextureConfig::WrapMode wrap_t; | ||||
|     u32 border_color = 0; | ||||
|     u32 lod_min = 0; | ||||
|     u32 lod_max = 0; | ||||
|     s32 lod_bias = 0; | ||||
| 
 | ||||
|     auto operator<=>(const SamplerParams&) const noexcept = default; | ||||
| 
 | ||||
|     const u64 Hash() const { | ||||
|         return Common::ComputeHash64(this, sizeof(SamplerParams)); | ||||
|     } | ||||
| }; | ||||
| static_assert(std::has_unique_object_representations_v<SamplerParams>, | ||||
|               "SamplerParams is not suitable for hashing"); | ||||
| 
 | ||||
| } // namespace VideoCore
 | ||||
| 
 | ||||
| namespace std { | ||||
| template <> | ||||
| struct hash<VideoCore::SamplerParams> { | ||||
|     std::size_t operator()(const VideoCore::SamplerParams& params) const noexcept { | ||||
|         return params.Hash(); | ||||
|     } | ||||
| }; | ||||
| } // namespace std
 | ||||
|  | @ -45,13 +45,16 @@ bool SurfaceBase::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fil | |||
| } | ||||
| 
 | ||||
| bool SurfaceBase::CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const { | ||||
|     SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); | ||||
|     const SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); | ||||
|     ASSERT(subrect_params.GetInterval() == copy_interval); | ||||
|     if (CanSubRect(subrect_params)) | ||||
|         return true; | ||||
| 
 | ||||
|     if (CanFill(dest_surface, copy_interval)) | ||||
|     if (CanSubRect(subrect_params)) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     if (CanFill(dest_surface, copy_interval)) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
|  | @ -102,6 +105,23 @@ SurfaceInterval SurfaceBase::GetCopyableInterval(const SurfaceParams& params) co | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| Extent SurfaceBase::RealExtent(bool scaled) { | ||||
|     const bool is_custom = IsCustom(); | ||||
|     u32 real_width = width; | ||||
|     u32 real_height = height; | ||||
|     if (is_custom) { | ||||
|         real_width = material->width; | ||||
|         real_height = material->height; | ||||
|     } else if (scaled) { | ||||
|         real_width = GetScaledWidth(); | ||||
|         real_height = GetScaledHeight(); | ||||
|     } | ||||
|     return Extent{ | ||||
|         .width = real_width, | ||||
|         .height = real_height, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| bool SurfaceBase::HasNormalMap() const noexcept { | ||||
|     return material && material->Map(MapType::Normal) != nullptr; | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #include <boost/icl/interval_set.hpp> | ||||
| #include "video_core/rasterizer_cache/surface_params.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
|  | @ -13,6 +14,15 @@ using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterva | |||
| 
 | ||||
| struct Material; | ||||
| 
 | ||||
| enum class SurfaceFlagBits : u32 { | ||||
|     Registered = 1 << 0, ///< Surface is registed in the rasterizer cache.
 | ||||
|     Picked = 1 << 1,     ///< Surface has been picked when searching for a match.
 | ||||
|     Tracked = 1 << 2,    ///< Surface is part of a texture cube and should be tracked.
 | ||||
|     Custom = 1 << 3,     ///< Surface texture has been replaced with a custom texture.
 | ||||
|     ShadowMap = 1 << 4,  ///< Surface is used during shadow rendering.
 | ||||
| }; | ||||
| DECLARE_ENUM_FLAG_OPERATORS(SurfaceFlagBits); | ||||
| 
 | ||||
| class SurfaceBase : public SurfaceParams { | ||||
| public: | ||||
|     SurfaceBase(const SurfaceParams& params); | ||||
|  | @ -30,19 +40,27 @@ public: | |||
|     /// Returns the clear value used to validate another surface from this fill surface
 | ||||
|     ClearValue MakeClearValue(PAddr copy_addr, PixelFormat dst_format); | ||||
| 
 | ||||
|     /// Returns the internal surface extent.
 | ||||
|     Extent RealExtent(bool scaled = true); | ||||
| 
 | ||||
|     /// Returns true if the surface contains a custom material with a normal map.
 | ||||
|     bool HasNormalMap() const noexcept; | ||||
| 
 | ||||
|     bool Overlaps(PAddr overlap_addr, size_t overlap_size) const noexcept { | ||||
|         const PAddr overlap_end = overlap_addr + static_cast<PAddr>(overlap_size); | ||||
|         return addr < overlap_end && overlap_addr < end; | ||||
|     } | ||||
| 
 | ||||
|     u64 ModificationTick() const noexcept { | ||||
|         return modification_tick; | ||||
|     } | ||||
| 
 | ||||
|     bool IsCustom() const noexcept { | ||||
|         return is_custom && custom_format != CustomPixelFormat::Invalid; | ||||
|         return True(flags & SurfaceFlagBits::Custom) && custom_format != CustomPixelFormat::Invalid; | ||||
|     } | ||||
| 
 | ||||
|     bool IsRegionValid(SurfaceInterval interval) const { | ||||
|         return (invalid_regions.find(interval) == invalid_regions.end()); | ||||
|         return invalid_regions.find(interval) == invalid_regions.end(); | ||||
|     } | ||||
| 
 | ||||
|     void MarkValid(SurfaceInterval interval) { | ||||
|  | @ -65,8 +83,7 @@ private: | |||
|     std::array<u8, 4> MakeFillBuffer(PAddr copy_addr); | ||||
| 
 | ||||
| public: | ||||
|     bool registered = false; | ||||
|     bool is_custom = false; | ||||
|     SurfaceFlagBits flags{}; | ||||
|     const Material* material = nullptr; | ||||
|     SurfaceRegions invalid_regions; | ||||
|     u32 fill_size = 0; | ||||
|  |  | |||
|  | @ -15,14 +15,23 @@ bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const { | |||
| } | ||||
| 
 | ||||
| bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const { | ||||
|     const u32 level = LevelOf(sub_surface.addr); | ||||
|     return sub_surface.addr >= addr && sub_surface.end <= end && | ||||
|            sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid && | ||||
|            sub_surface.is_tiled == is_tiled && | ||||
|            (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && | ||||
|            (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) && | ||||
|            (sub_surface.addr - mipmap_offsets[level]) % BytesInPixels(is_tiled ? 64 : 1) == 0 && | ||||
|            (sub_surface.stride == (stride >> level) || | ||||
|             sub_surface.height <= (is_tiled ? 8u : 1u)) && | ||||
|            GetSubRect(sub_surface).right <= stride; | ||||
| } | ||||
| 
 | ||||
| bool SurfaceParams::CanReinterpret(const SurfaceParams& other_surface) { | ||||
|     return other_surface.addr >= addr && other_surface.end <= end && | ||||
|            pixel_format != PixelFormat::Invalid && GetFormatBpp() == other_surface.GetFormatBpp() && | ||||
|            other_surface.is_tiled == is_tiled && | ||||
|            (other_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0; | ||||
| } | ||||
| 
 | ||||
| bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const { | ||||
|     return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format && | ||||
|            addr <= expanded_surface.end && expanded_surface.addr <= end && | ||||
|  | @ -206,7 +215,9 @@ SurfaceInterval SurfaceParams::LevelInterval(u32 level) const { | |||
| } | ||||
| 
 | ||||
| u32 SurfaceParams::LevelOf(PAddr level_addr) const { | ||||
|     ASSERT(level_addr >= addr && level_addr <= end); | ||||
|     if (level_addr < addr || level_addr > end) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     u32 level = levels - 1; | ||||
|     while (mipmap_offsets[level] > level_addr) { | ||||
|  |  | |||
|  | @ -4,11 +4,15 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <boost/icl/right_open_interval.hpp> | ||||
| #include "common/math_util.h" | ||||
| #include "video_core/custom_textures/custom_format.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
| using SurfaceInterval = boost::icl::right_open_interval<PAddr>; | ||||
| 
 | ||||
| constexpr std::size_t MAX_PICA_LEVELS = 8; | ||||
| 
 | ||||
| class SurfaceParams { | ||||
|  | @ -19,6 +23,9 @@ public: | |||
|     /// Returns true if sub_surface is a subrect of params
 | ||||
|     bool CanSubRect(const SurfaceParams& sub_surface) const; | ||||
| 
 | ||||
|     /// Returns true if other_surface can be used for reinterpretion.
 | ||||
|     bool CanReinterpret(const SurfaceParams& other_surface); | ||||
| 
 | ||||
|     /// Returns true if params can be expanded to match expanded_surface
 | ||||
|     bool CanExpand(const SurfaceParams& expanded_surface) const; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,28 +4,31 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <compare> | ||||
| #include <span> | ||||
| #include <boost/icl/right_open_interval.hpp> | ||||
| #include "common/hash.h" | ||||
| #include "common/math_util.h" | ||||
| #include "common/slot_vector.h" | ||||
| #include "common/vector_math.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| #include "video_core/regs_texturing.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| 
 | ||||
| using SurfaceInterval = boost::icl::right_open_interval<PAddr>; | ||||
| using SurfaceId = Common::SlotId; | ||||
| using SamplerId = Common::SlotId; | ||||
| 
 | ||||
| /// Fake surface ID for null surfaces
 | ||||
| constexpr SurfaceId NULL_SURFACE_ID{0}; | ||||
| /// Fake surface ID for null cube surfaces
 | ||||
| constexpr SurfaceId NULL_SURFACE_CUBE_ID{1}; | ||||
| /// Fake sampler ID for null samplers
 | ||||
| constexpr SamplerId NULL_SAMPLER_ID{0}; | ||||
| 
 | ||||
| struct Offset { | ||||
|     constexpr auto operator<=>(const Offset&) const noexcept = default; | ||||
| 
 | ||||
|     u32 x = 0; | ||||
|     u32 y = 0; | ||||
| }; | ||||
| 
 | ||||
| struct Extent { | ||||
|     constexpr auto operator<=>(const Extent&) const noexcept = default; | ||||
| 
 | ||||
|     u32 width = 1; | ||||
|     u32 height = 1; | ||||
| }; | ||||
|  | @ -71,9 +74,9 @@ struct BufferTextureCopy { | |||
| }; | ||||
| 
 | ||||
| struct StagingData { | ||||
|     u32 size = 0; | ||||
|     std::span<u8> mapped{}; | ||||
|     u64 buffer_offset = 0; | ||||
|     u32 size; | ||||
|     std::span<u8> mapped; | ||||
|     u64 buffer_offset; | ||||
| }; | ||||
| 
 | ||||
| struct TextureCubeConfig { | ||||
|  |  | |||
|  | @ -2,12 +2,16 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/scope_exit.h" | ||||
| #include "common/settings.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| #include "video_core/renderer_opengl/gl_blit_helper.h" | ||||
| #include "video_core/renderer_opengl/gl_driver.h" | ||||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
| #include "video_core/renderer_opengl/gl_texture_runtime.h" | ||||
| 
 | ||||
| #include "video_core/host_shaders/format_reinterpreter/d24s8_to_rgba8_frag.h" | ||||
| #include "video_core/host_shaders/format_reinterpreter/rgba4_to_rgb5a1_frag.h" | ||||
| #include "video_core/host_shaders/full_screen_triangle_vert.h" | ||||
| #include "video_core/host_shaders/texture_filtering/bicubic_frag.h" | ||||
| #include "video_core/host_shaders/texture_filtering/nearest_neighbor_frag.h" | ||||
|  | @ -49,8 +53,8 @@ OGLProgram CreateProgram(std::string_view frag) { | |||
| 
 | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| BlitHelper::BlitHelper(TextureRuntime& runtime_) | ||||
|     : runtime{runtime_}, linear_sampler{CreateSampler(GL_LINEAR)}, | ||||
| BlitHelper::BlitHelper(const Driver& driver_) | ||||
|     : driver{driver_}, linear_sampler{CreateSampler(GL_LINEAR)}, | ||||
|       nearest_sampler{CreateSampler(GL_NEAREST)}, bicubic_program{CreateProgram( | ||||
|                                                       HostShaders::BICUBIC_FRAG)}, | ||||
|       nearest_program{CreateProgram(HostShaders::NEAREST_NEIGHBOR_FRAG)}, | ||||
|  | @ -58,34 +62,104 @@ BlitHelper::BlitHelper(TextureRuntime& runtime_) | |||
|       xbrz_program{CreateProgram(HostShaders::XBRZ_FREESCALE_FRAG)}, | ||||
|       gradient_x_program{CreateProgram(HostShaders::X_GRADIENT_FRAG)}, | ||||
|       gradient_y_program{CreateProgram(HostShaders::Y_GRADIENT_FRAG)}, | ||||
|       refine_program{CreateProgram(HostShaders::REFINE_FRAG)} { | ||||
|       refine_program{CreateProgram(HostShaders::REFINE_FRAG)}, | ||||
|       d24s8_to_rgba8{CreateProgram(HostShaders::D24S8_TO_RGBA8_FRAG)}, | ||||
|       rgba4_to_rgb5a1{CreateProgram(HostShaders::RGBA4_TO_RGB5A1_FRAG)} { | ||||
|     vao.Create(); | ||||
|     filter_fbo.Create(); | ||||
|     draw_fbo.Create(); | ||||
|     state.draw.vertex_array = vao.handle; | ||||
|     for (u32 i = 0; i < 3; i++) { | ||||
|         state.texture_units[i].sampler = i == 2 ? nearest_sampler.handle : linear_sampler.handle; | ||||
|     } | ||||
|     if (driver.IsOpenGLES()) { | ||||
|         LOG_INFO(Render_OpenGL, | ||||
|                  "Texture views are unsupported, reinterpretation will do intermediate copy"); | ||||
|         temp_tex.Create(); | ||||
|         use_texture_view = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| BlitHelper::~BlitHelper() = default; | ||||
| 
 | ||||
| bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, | ||||
|                                       const VideoCore::TextureBlit& blit) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
| 
 | ||||
|     state.texture_units[0].texture_2d = source.Handle(); | ||||
|     state.texture_units[0].sampler = 0; | ||||
|     state.texture_units[1].sampler = 0; | ||||
| 
 | ||||
|     if (use_texture_view) { | ||||
|         temp_tex.Create(); | ||||
|         glActiveTexture(GL_TEXTURE1); | ||||
|         glTextureView(temp_tex.handle, GL_TEXTURE_2D, source.Handle(), GL_DEPTH24_STENCIL8, 0, 1, 0, | ||||
|                       1); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|     } else if (blit.src_rect.top > temp_rect.top || blit.src_rect.right > temp_rect.right) { | ||||
|         temp_tex.Release(); | ||||
|         temp_tex.Create(); | ||||
|         state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|         state.Apply(); | ||||
|         glActiveTexture(GL_TEXTURE1); | ||||
|         glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, blit.src_rect.right, | ||||
|                        blit.src_rect.top); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|         temp_rect = blit.src_rect; | ||||
|     } | ||||
|     state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     if (!use_texture_view) { | ||||
|         glCopyImageSubData(source.Handle(), GL_TEXTURE_2D, 0, blit.src_rect.left, | ||||
|                            blit.src_rect.bottom, 0, temp_tex.handle, GL_TEXTURE_2D, 0, | ||||
|                            blit.src_rect.left, blit.src_rect.bottom, 0, blit.src_rect.GetWidth(), | ||||
|                            blit.src_rect.GetHeight(), 1); | ||||
|     } | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); | ||||
| 
 | ||||
|     SetParams(d24s8_to_rgba8, source.RealExtent(), blit.src_rect); | ||||
|     Draw(d24s8_to_rgba8, dest.Handle(), draw_fbo.handle, 0, blit.dst_rect); | ||||
| 
 | ||||
|     if (use_texture_view) { | ||||
|         temp_tex.Release(); | ||||
|     } | ||||
| 
 | ||||
|     // Restore the sampler handles
 | ||||
|     state.texture_units[0].sampler = linear_sampler.handle; | ||||
|     state.texture_units[1].sampler = linear_sampler.handle; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, | ||||
|                                       const VideoCore::TextureBlit& blit) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
| 
 | ||||
|     state.texture_units[0].texture_2d = source.Handle(); | ||||
| 
 | ||||
|     SetParams(rgba4_to_rgb5a1, source.RealExtent(), blit.src_rect); | ||||
|     Draw(rgba4_to_rgb5a1, dest.Handle(), draw_fbo.handle, 0, blit.dst_rect); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     // Filtering to depth stencil surfaces isn't supported.
 | ||||
|     if (surface.type == SurfaceType::Depth || surface.type == SurfaceType::DepthStencil) { | ||||
|     const auto filter = Settings::values.texture_filter.GetValue(); | ||||
|     const bool is_depth = | ||||
|         surface.type == SurfaceType::Depth || surface.type == SurfaceType::DepthStencil; | ||||
|     if (filter == Settings::TextureFilter::None || is_depth) { | ||||
|         return false; | ||||
|     } | ||||
|     // Avoid filtering for mipmaps as the result often looks terrible.
 | ||||
|     if (blit.src_level != 0) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     state.texture_units[0].texture_2d = surface.Handle(0); | ||||
| 
 | ||||
|     const auto filter{Settings::values.texture_filter.GetValue()}; | ||||
|     switch (filter) { | ||||
|     case TextureFilter::None: | ||||
|         break; | ||||
|     case TextureFilter::Anime4K: | ||||
|         FilterAnime4K(surface, blit); | ||||
|         break; | ||||
|  | @ -101,15 +175,19 @@ bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) { | |||
|     case TextureFilter::xBRZ: | ||||
|         FilterXbrz(surface, blit); | ||||
|         break; | ||||
|     default: | ||||
|         LOG_ERROR(Render_OpenGL, "Unknown texture filter {}", filter); | ||||
|     } | ||||
| 
 | ||||
|     prev_state.Apply(); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     static constexpr u8 internal_scale_factor = 2; | ||||
| 
 | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
| 
 | ||||
|     const auto& tuple = surface.Tuple(); | ||||
|     const u32 src_width = blit.src_rect.GetWidth(); | ||||
|     const u32 src_height = blit.src_rect.GetHeight(); | ||||
|  | @ -149,7 +227,7 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b | |||
|     Draw(gradient_y_program, LUMAD.tex.handle, LUMAD.fbo.handle, 0, temp_rect); | ||||
| 
 | ||||
|     // refine pass
 | ||||
|     Draw(refine_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
|     Draw(refine_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| 
 | ||||
|     // These will have handles from the previous texture that was filtered, reset them to avoid
 | ||||
|     // binding invalid textures.
 | ||||
|  | @ -160,25 +238,36 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b | |||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     SetParams(bicubic_program, surface.Extent(), blit.src_rect); | ||||
|     Draw(bicubic_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|     state.texture_units[0].texture_2d = surface.Handle(0); | ||||
|     SetParams(bicubic_program, surface.RealExtent(false), blit.src_rect); | ||||
|     Draw(bicubic_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|     state.texture_units[2].texture_2d = surface.Handle(0); | ||||
|     SetParams(nearest_program, surface.Extent(), blit.src_rect); | ||||
|     Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
|     SetParams(nearest_program, surface.RealExtent(false), blit.src_rect); | ||||
|     Draw(nearest_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     SetParams(scale_force_program, surface.Extent(), blit.src_rect); | ||||
|     Draw(scale_force_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|     state.texture_units[0].texture_2d = surface.Handle(0); | ||||
|     SetParams(scale_force_program, surface.RealExtent(false), blit.src_rect); | ||||
|     Draw(scale_force_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) { | ||||
|     const OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|     state.texture_units[0].texture_2d = surface.Handle(0); | ||||
|     glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale)); | ||||
|     SetParams(xbrz_program, surface.Extent(), blit.src_rect); | ||||
|     Draw(xbrz_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
|     SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect); | ||||
|     Draw(xbrz_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect); | ||||
| } | ||||
| 
 | ||||
| void BlitHelper::SetParams(OGLProgram& program, const VideoCore::Extent& src_extent, | ||||
|  | @ -206,7 +295,7 @@ void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 d | |||
|                            dst_level); | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||
| 
 | ||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||
|     glDrawArrays(GL_TRIANGLES, 0, 3); | ||||
| } | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
|  | @ -15,16 +15,20 @@ struct TextureBlit; | |||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| class TextureRuntime; | ||||
| class Driver; | ||||
| class Surface; | ||||
| 
 | ||||
| class BlitHelper { | ||||
| public: | ||||
|     BlitHelper(TextureRuntime& runtime); | ||||
|     explicit BlitHelper(const Driver& driver); | ||||
|     ~BlitHelper(); | ||||
| 
 | ||||
|     bool Filter(Surface& surface, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
|     bool ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
|     bool ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
| private: | ||||
|     void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
|  | @ -43,10 +47,10 @@ private: | |||
|               Common::Rectangle<u32> dst_rect); | ||||
| 
 | ||||
| private: | ||||
|     TextureRuntime& runtime; | ||||
|     const Driver& driver; | ||||
|     OGLVertexArray vao; | ||||
|     OpenGLState state; | ||||
|     OGLFramebuffer filter_fbo; | ||||
|     OGLFramebuffer draw_fbo; | ||||
|     OGLSampler linear_sampler; | ||||
|     OGLSampler nearest_sampler; | ||||
| 
 | ||||
|  | @ -57,6 +61,12 @@ private: | |||
|     OGLProgram gradient_x_program; | ||||
|     OGLProgram gradient_y_program; | ||||
|     OGLProgram refine_program; | ||||
|     OGLProgram d24s8_to_rgba8; | ||||
|     OGLProgram rgba4_to_rgb5a1; | ||||
| 
 | ||||
|     OGLTexture temp_tex; | ||||
|     Common::Rectangle<u32> temp_rect{}; | ||||
|     bool use_texture_view{true}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
|  | @ -1,134 +0,0 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/scope_exit.h" | ||||
| #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
| #include "video_core/renderer_opengl/gl_texture_runtime.h" | ||||
| 
 | ||||
| #include "video_core/host_shaders/format_reinterpreter/d24s8_to_rgba8_frag.h" | ||||
| #include "video_core/host_shaders/format_reinterpreter/fullscreen_quad_vert.h" | ||||
| #include "video_core/host_shaders/format_reinterpreter/rgba4_to_rgb5a1_frag.h" | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| RGBA4toRGB5A1::RGBA4toRGB5A1() { | ||||
|     program.Create(HostShaders::FULLSCREEN_QUAD_VERT, HostShaders::RGBA4_TO_RGB5A1_FRAG); | ||||
|     dst_size_loc = glGetUniformLocation(program.handle, "dst_size"); | ||||
|     src_size_loc = glGetUniformLocation(program.handle, "src_size"); | ||||
|     src_offset_loc = glGetUniformLocation(program.handle, "src_offset"); | ||||
|     vao.Create(); | ||||
| } | ||||
| 
 | ||||
| void RGBA4toRGB5A1::Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest, | ||||
|                                 Common::Rectangle<u32> dst_rect) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
| 
 | ||||
|     OpenGLState state; | ||||
|     state.texture_units[0].texture_2d = source.Handle(); | ||||
|     state.draw.draw_framebuffer = draw_fbo.handle; | ||||
|     state.draw.shader_program = program.handle; | ||||
|     state.draw.vertex_array = vao.handle; | ||||
|     state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom), | ||||
|                       static_cast<GLsizei>(dst_rect.GetWidth()), | ||||
|                       static_cast<GLsizei>(dst_rect.GetHeight())}; | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dest.Handle(), | ||||
|                            0); | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||
| 
 | ||||
|     glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight()); | ||||
|     glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight()); | ||||
|     glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom); | ||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||
| } | ||||
| 
 | ||||
| ShaderD24S8toRGBA8::ShaderD24S8toRGBA8() { | ||||
|     program.Create(HostShaders::FULLSCREEN_QUAD_VERT, HostShaders::D24S8_TO_RGBA8_FRAG); | ||||
|     dst_size_loc = glGetUniformLocation(program.handle, "dst_size"); | ||||
|     src_size_loc = glGetUniformLocation(program.handle, "src_size"); | ||||
|     src_offset_loc = glGetUniformLocation(program.handle, "src_offset"); | ||||
|     vao.Create(); | ||||
| 
 | ||||
|     auto state = OpenGLState::GetCurState(); | ||||
|     auto cur_program = state.draw.shader_program; | ||||
|     state.draw.shader_program = program.handle; | ||||
|     state.Apply(); | ||||
|     glUniform1i(glGetUniformLocation(program.handle, "stencil"), 1); | ||||
|     state.draw.shader_program = cur_program; | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     // Nvidia seem to be the only one to support D24S8 views, at least on windows
 | ||||
|     // so for everyone else it will do an intermediate copy before running through the shader
 | ||||
|     std::string_view vendor{reinterpret_cast<const char*>(glGetString(GL_VENDOR))}; | ||||
|     if (vendor.find("NVIDIA") != vendor.npos) { | ||||
|         use_texture_view = true; | ||||
|     } else { | ||||
|         LOG_INFO(Render_OpenGL, | ||||
|                  "Texture views are unsupported, reinterpretation will do intermediate copy"); | ||||
|         temp_tex.Create(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ShaderD24S8toRGBA8::Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, | ||||
|                                      Surface& dest, Common::Rectangle<u32> dst_rect) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
| 
 | ||||
|     OpenGLState state; | ||||
|     state.texture_units[0].texture_2d = source.Handle(); | ||||
| 
 | ||||
|     if (use_texture_view) { | ||||
|         temp_tex.Create(); | ||||
|         glActiveTexture(GL_TEXTURE1); | ||||
|         glTextureView(temp_tex.handle, GL_TEXTURE_2D, source.Handle(), GL_DEPTH24_STENCIL8, 0, 1, 0, | ||||
|                       1); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|     } else if (src_rect.top > temp_rect.top || src_rect.right > temp_rect.right) { | ||||
|         temp_tex.Release(); | ||||
|         temp_tex.Create(); | ||||
|         state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|         state.Apply(); | ||||
|         glActiveTexture(GL_TEXTURE1); | ||||
|         glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, src_rect.right, src_rect.top); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|         temp_rect = src_rect; | ||||
|     } | ||||
| 
 | ||||
|     state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|     state.draw.draw_framebuffer = draw_fbo.handle; | ||||
|     state.draw.shader_program = program.handle; | ||||
|     state.draw.vertex_array = vao.handle; | ||||
|     state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom), | ||||
|                       static_cast<GLsizei>(dst_rect.GetWidth()), | ||||
|                       static_cast<GLsizei>(dst_rect.GetHeight())}; | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     if (!use_texture_view) { | ||||
|         glCopyImageSubData(source.Handle(), GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, | ||||
|                            temp_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0, | ||||
|                            src_rect.GetWidth(), src_rect.GetHeight(), 1); | ||||
|     } | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); | ||||
| 
 | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dest.Handle(), | ||||
|                            0); | ||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||
| 
 | ||||
|     glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight()); | ||||
|     glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight()); | ||||
|     glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom); | ||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||
| 
 | ||||
|     if (use_texture_view) { | ||||
|         temp_tex.Release(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  | @ -1,76 +0,0 @@ | |||
| // Copyright 2022 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "common/math_util.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
| class Surface; | ||||
| 
 | ||||
| class FormatReinterpreterBase { | ||||
| public: | ||||
|     FormatReinterpreterBase() { | ||||
|         read_fbo.Create(); | ||||
|         draw_fbo.Create(); | ||||
|     } | ||||
| 
 | ||||
|     virtual ~FormatReinterpreterBase() = default; | ||||
| 
 | ||||
|     virtual VideoCore::PixelFormat GetSourceFormat() const = 0; | ||||
|     virtual void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest, | ||||
|                              Common::Rectangle<u32> dst_rect) = 0; | ||||
| 
 | ||||
| protected: | ||||
|     OGLFramebuffer read_fbo; | ||||
|     OGLFramebuffer draw_fbo; | ||||
| }; | ||||
| 
 | ||||
| using ReinterpreterList = std::vector<std::unique_ptr<FormatReinterpreterBase>>; | ||||
| 
 | ||||
| class RGBA4toRGB5A1 final : public FormatReinterpreterBase { | ||||
| public: | ||||
|     RGBA4toRGB5A1(); | ||||
| 
 | ||||
|     VideoCore::PixelFormat GetSourceFormat() const override { | ||||
|         return VideoCore::PixelFormat::RGBA4; | ||||
|     } | ||||
| 
 | ||||
|     void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest, | ||||
|                      Common::Rectangle<u32> dst_rect) override; | ||||
| 
 | ||||
| private: | ||||
|     OGLProgram program; | ||||
|     GLint dst_size_loc{-1}; | ||||
|     GLint src_size_loc{-1}; | ||||
|     GLint src_offset_loc{-1}; | ||||
|     OGLVertexArray vao; | ||||
| }; | ||||
| 
 | ||||
| class ShaderD24S8toRGBA8 final : public FormatReinterpreterBase { | ||||
| public: | ||||
|     ShaderD24S8toRGBA8(); | ||||
| 
 | ||||
|     VideoCore::PixelFormat GetSourceFormat() const override { | ||||
|         return VideoCore::PixelFormat::D24S8; | ||||
|     } | ||||
| 
 | ||||
|     void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest, | ||||
|                      Common::Rectangle<u32> dst_rect) override; | ||||
| 
 | ||||
| private: | ||||
|     bool use_texture_view{}; | ||||
|     OGLProgram program{}; | ||||
|     GLint dst_size_loc{-1}; | ||||
|     GLint src_size_loc{-1}; | ||||
|     GLint src_offset_loc{-1}; | ||||
|     OGLVertexArray vao{}; | ||||
|     OGLTexture temp_tex{}; | ||||
|     Common::Rectangle<u32> temp_rect{0, 0, 0, 0}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  | @ -25,7 +25,7 @@ MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(255, 128, | |||
| MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(192, 128, 128)); | ||||
| MICROPROFILE_DEFINE(OpenGL_GS, "OpenGL", "Geometry Shader Setup", MP_RGB(128, 192, 128)); | ||||
| MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); | ||||
| MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); | ||||
| MICROPROFILE_DEFINE(OpenGL_Display, "OpenGL", "Display", MP_RGB(128, 128, 192)); | ||||
| 
 | ||||
| using VideoCore::SurfaceType; | ||||
| 
 | ||||
|  | @ -97,16 +97,6 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, | |||
|     u8 framebuffer_data[4] = {0, 0, 0, 1}; | ||||
|     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data); | ||||
| 
 | ||||
|     // Create sampler objects
 | ||||
|     for (std::size_t i = 0; i < texture_samplers.size(); ++i) { | ||||
|         texture_samplers[i].Create(); | ||||
|         state.texture_units[i].sampler = texture_samplers[i].sampler.handle; | ||||
|     } | ||||
| 
 | ||||
|     // Create cubemap texture and sampler objects
 | ||||
|     texture_cube_sampler.Create(); | ||||
|     state.texture_cube_unit.sampler = texture_cube_sampler.sampler.handle; | ||||
| 
 | ||||
|     // Generate VAO
 | ||||
|     sw_vao.Create(); | ||||
|     hw_vao.Create(); | ||||
|  | @ -251,14 +241,14 @@ void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         PAddr data_addr = | ||||
|         const PAddr data_addr = | ||||
|             base_address + loader.data_offset + (vs_input_index_min * loader.byte_count); | ||||
| 
 | ||||
|         u32 vertex_num = vs_input_index_max - vs_input_index_min + 1; | ||||
|         u32 data_size = loader.byte_count * vertex_num; | ||||
|         const u32 vertex_num = vs_input_index_max - vs_input_index_min + 1; | ||||
|         const u32 data_size = loader.byte_count * vertex_num; | ||||
| 
 | ||||
|         res_cache.FlushRegion(data_addr, data_size, nullptr); | ||||
|         std::memcpy(array_ptr, VideoCore::g_memory->GetPhysicalPointer(data_addr), data_size); | ||||
|         res_cache.FlushRegion(data_addr, data_size); | ||||
|         std::memcpy(array_ptr, memory.GetPhysicalPointer(data_addr), data_size); | ||||
| 
 | ||||
|         array_ptr += data_size; | ||||
|         buffer_offset += data_size; | ||||
|  | @ -287,8 +277,7 @@ void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, | |||
| 
 | ||||
| bool RasterizerOpenGL::SetupVertexShader() { | ||||
|     MICROPROFILE_SCOPE(OpenGL_VS); | ||||
|     return shader_program_manager->UseProgrammableVertexShader(Pica::g_state.regs, | ||||
|                                                                Pica::g_state.vs); | ||||
|     return shader_program_manager->UseProgrammableVertexShader(regs, Pica::g_state.vs); | ||||
| } | ||||
| 
 | ||||
| bool RasterizerOpenGL::SetupGeometryShader() { | ||||
|  | @ -400,8 +389,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { | |||
|     const Framebuffer framebuffer = | ||||
|         res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb); | ||||
|     const bool has_color = framebuffer.HasAttachment(SurfaceType::Color); | ||||
|     const bool has_depth_stencil = framebuffer.HasAttachment(SurfaceType::DepthStencil); | ||||
|     if (!has_color && (shadow_rendering || !has_depth_stencil)) { | ||||
|     if (!has_color && shadow_rendering) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -520,8 +508,9 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) { | |||
|         if (texture_index == 0) { | ||||
|             switch (texture.config.type.Value()) { | ||||
|             case TextureType::Shadow2D: { | ||||
|                 auto surface = res_cache.GetTextureSurface(texture); | ||||
|                 state.image_shadow_texture_px = surface->Handle(); | ||||
|                 Surface& surface = res_cache.GetTextureSurface(texture); | ||||
|                 surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap; | ||||
|                 state.image_shadow_texture_px = surface.Handle(); | ||||
|                 continue; | ||||
|             } | ||||
|             case TextureType::ShadowCube: { | ||||
|  | @ -538,22 +527,14 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) { | |||
|         } | ||||
| 
 | ||||
|         // Sync texture unit sampler
 | ||||
|         texture_samplers[texture_index].SyncWithConfig(texture.config); | ||||
|         Sampler& sampler = res_cache.GetSampler(texture.config); | ||||
|         state.texture_units[texture_index].sampler = sampler.Handle(); | ||||
| 
 | ||||
|         // Bind the texture provided by the rasterizer cache
 | ||||
|         auto surface = res_cache.GetTextureSurface(texture); | ||||
|         if (!surface) { | ||||
|             // Can occur when texture addr is null or its memory is unmapped/invalid
 | ||||
|             // HACK: In this case, the correct behaviour for the PICA is to use the last
 | ||||
|             // rendered colour. But because this would be impractical to implement, the
 | ||||
|             // next best alternative is to use a clear texture, essentially skipping
 | ||||
|             // the geometry in question.
 | ||||
|             // For example: a bug in Pokemon X/Y causes NULL-texture squares to be drawn
 | ||||
|             // on the male character's face, which in the OpenGL default appear black.
 | ||||
|             state.texture_units[texture_index].texture_2d = default_texture; | ||||
|         } else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) { | ||||
|             BindMaterial(texture_index, *surface); | ||||
|             state.texture_units[texture_index].texture_2d = surface->Handle(); | ||||
|         Surface& surface = res_cache.GetTextureSurface(texture); | ||||
|         if (!IsFeedbackLoop(texture_index, framebuffer, surface)) { | ||||
|             BindMaterial(texture_index, surface); | ||||
|             state.texture_units[texture_index].texture_2d = surface.Handle(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -570,8 +551,10 @@ void RasterizerOpenGL::BindShadowCube(const Pica::TexturingRegs::FullTextureConf | |||
|         const u32 binding = static_cast<u32>(face); | ||||
|         info.physical_address = regs.texturing.GetCubePhysicalAddress(face); | ||||
| 
 | ||||
|         auto surface = res_cache.GetTextureSurface(info); | ||||
|         state.image_shadow_texture[binding] = surface->Handle(); | ||||
|         VideoCore::SurfaceId surface_id = res_cache.GetTextureSurface(info); | ||||
|         Surface& surface = res_cache.GetSurface(surface_id); | ||||
|         surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap; | ||||
|         state.image_shadow_texture[binding] = surface.Handle(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -589,10 +572,11 @@ void RasterizerOpenGL::BindTextureCube(const Pica::TexturingRegs::FullTextureCon | |||
|         .format = texture.format, | ||||
|     }; | ||||
| 
 | ||||
|     auto surface = res_cache.GetTextureCube(config); | ||||
|     texture_cube_sampler.SyncWithConfig(texture.config); | ||||
|     Surface& surface = res_cache.GetTextureCube(config); | ||||
|     Sampler& sampler = res_cache.GetSampler(texture.config); | ||||
| 
 | ||||
|     state.texture_cube_unit.texture_cube = surface->Handle(); | ||||
|     state.texture_cube_unit.texture_cube = surface.Handle(); | ||||
|     state.texture_cube_unit.sampler = sampler.Handle(); | ||||
|     state.texture_units[0].texture_2d = 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -608,7 +592,7 @@ void RasterizerOpenGL::BindMaterial(u32 texture_index, Surface& surface) { | |||
|         glBindSampler(unit.id, sampler); | ||||
|     }; | ||||
| 
 | ||||
|     const GLuint sampler = texture_samplers[texture_index].sampler.handle; | ||||
|     const GLuint sampler = state.texture_units[texture_index].sampler; | ||||
|     if (surface.HasNormalMap()) { | ||||
|         if (regs.lighting.disable) { | ||||
|             LOG_WARNING(Render_OpenGL, "Custom normal map used but scene has no light enabled"); | ||||
|  | @ -726,24 +710,20 @@ void RasterizerOpenGL::NotifyFixedFunctionPicaRegisterChanged(u32 id) { | |||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::FlushAll() { | ||||
|     MICROPROFILE_SCOPE(OpenGL_CacheManagement); | ||||
|     res_cache.FlushAll(); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { | ||||
|     MICROPROFILE_SCOPE(OpenGL_CacheManagement); | ||||
|     res_cache.FlushRegion(addr, size); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { | ||||
|     MICROPROFILE_SCOPE(OpenGL_CacheManagement); | ||||
|     res_cache.InvalidateRegion(addr, size, nullptr); | ||||
|     res_cache.InvalidateRegion(addr, size); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { | ||||
|     MICROPROFILE_SCOPE(OpenGL_CacheManagement); | ||||
|     res_cache.FlushRegion(addr, size); | ||||
|     res_cache.InvalidateRegion(addr, size, nullptr); | ||||
|     res_cache.InvalidateRegion(addr, size); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::ClearAll(bool flush) { | ||||
|  | @ -768,7 +748,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con | |||
|     if (framebuffer_addr == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     MICROPROFILE_SCOPE(OpenGL_CacheManagement); | ||||
|     MICROPROFILE_SCOPE(OpenGL_Display); | ||||
| 
 | ||||
|     VideoCore::SurfaceParams src_params; | ||||
|     src_params.addr = framebuffer_addr; | ||||
|  | @ -779,85 +759,27 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con | |||
|     src_params.pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(config.color_format); | ||||
|     src_params.UpdateParams(); | ||||
| 
 | ||||
|     auto [src_surface, src_rect] = | ||||
|     const auto [src_surface_id, src_rect] = | ||||
|         res_cache.GetSurfaceSubRect(src_params, VideoCore::ScaleMatch::Ignore, true); | ||||
| 
 | ||||
|     if (src_surface == nullptr) { | ||||
|     if (!src_surface_id) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     const u32 scaled_width = src_surface->GetScaledWidth(); | ||||
|     const u32 scaled_height = src_surface->GetScaledHeight(); | ||||
|     const Surface& src_surface = res_cache.GetSurface(src_surface_id); | ||||
|     const u32 scaled_width = src_surface.GetScaledWidth(); | ||||
|     const u32 scaled_height = src_surface.GetScaledHeight(); | ||||
| 
 | ||||
|     screen_info.display_texcoords = Common::Rectangle<float>( | ||||
|         (float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width, | ||||
|         (float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width); | ||||
| 
 | ||||
|     screen_info.display_texture = src_surface->Handle(); | ||||
|     screen_info.display_texture = src_surface.Handle(); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SamplerInfo::Create() { | ||||
|     sampler.Create(); | ||||
|     mag_filter = min_filter = mip_filter = TextureConfig::Linear; | ||||
|     wrap_s = wrap_t = TextureConfig::Repeat; | ||||
|     border_color = 0; | ||||
|     lod_min = lod_max = 0; | ||||
| 
 | ||||
|     // default is 1000 and -1000
 | ||||
|     // Other attributes have correct defaults
 | ||||
|     glSamplerParameterf(sampler.handle, GL_TEXTURE_MAX_LOD, static_cast<float>(lod_max)); | ||||
|     glSamplerParameterf(sampler.handle, GL_TEXTURE_MIN_LOD, static_cast<float>(lod_min)); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SamplerInfo::SyncWithConfig( | ||||
|     const Pica::TexturingRegs::TextureConfig& config) { | ||||
| 
 | ||||
|     GLuint s = sampler.handle; | ||||
| 
 | ||||
|     if (mag_filter != config.mag_filter) { | ||||
|         mag_filter = config.mag_filter; | ||||
|         glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureMagFilterMode(mag_filter)); | ||||
|     } | ||||
| 
 | ||||
|     if (min_filter != config.min_filter || mip_filter != config.mip_filter) { | ||||
|         min_filter = config.min_filter; | ||||
|         mip_filter = config.mip_filter; | ||||
|         glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, | ||||
|                             PicaToGL::TextureMinFilterMode(min_filter, mip_filter)); | ||||
|     } | ||||
| 
 | ||||
|     if (wrap_s != config.wrap_s) { | ||||
|         wrap_s = config.wrap_s; | ||||
|         glSamplerParameteri(s, GL_TEXTURE_WRAP_S, PicaToGL::WrapMode(wrap_s)); | ||||
|     } | ||||
|     if (wrap_t != config.wrap_t) { | ||||
|         wrap_t = config.wrap_t; | ||||
|         glSamplerParameteri(s, GL_TEXTURE_WRAP_T, PicaToGL::WrapMode(wrap_t)); | ||||
|     } | ||||
| 
 | ||||
|     if (wrap_s == TextureConfig::ClampToBorder || wrap_t == TextureConfig::ClampToBorder) { | ||||
|         if (border_color != config.border_color.raw) { | ||||
|             border_color = config.border_color.raw; | ||||
|             auto gl_color = PicaToGL::ColorRGBA8(border_color); | ||||
|             glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.AsArray()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (lod_min != config.lod.min_level) { | ||||
|         lod_min = config.lod.min_level; | ||||
|         glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, static_cast<float>(lod_min)); | ||||
|     } | ||||
| 
 | ||||
|     if (lod_max != config.lod.max_level) { | ||||
|         lod_max = config.lod.max_level; | ||||
|         glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, static_cast<float>(lod_max)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SyncClipEnabled() { | ||||
|     state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0; | ||||
|     state.clip_distance[1] = regs.rasterizer.clip_enable != 0; | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SyncCullMode() { | ||||
|  | @ -885,7 +807,7 @@ void RasterizerOpenGL::SyncCullMode() { | |||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SyncBlendEnabled() { | ||||
|     state.blend.enabled = (Pica::g_state.regs.framebuffer.output_merger.alphablend_enable == 1); | ||||
|     state.blend.enabled = (regs.framebuffer.output_merger.alphablend_enable == 1); | ||||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SyncBlendFuncs() { | ||||
|  | @ -904,8 +826,7 @@ void RasterizerOpenGL::SyncBlendFuncs() { | |||
| } | ||||
| 
 | ||||
| void RasterizerOpenGL::SyncBlendColor() { | ||||
|     auto blend_color = | ||||
|         PicaToGL::ColorRGBA8(Pica::g_state.regs.framebuffer.output_merger.blend_const.raw); | ||||
|     auto blend_color = PicaToGL::ColorRGBA8(regs.framebuffer.output_merger.blend_const.raw); | ||||
|     state.blend.color.red = blend_color[0]; | ||||
|     state.blend.color.green = blend_color[1]; | ||||
|     state.blend.color.blue = blend_color[2]; | ||||
|  |  | |||
|  | @ -55,28 +55,6 @@ private: | |||
|     void SyncFixedState() override; | ||||
|     void NotifyFixedFunctionPicaRegisterChanged(u32 id) override; | ||||
| 
 | ||||
|     struct SamplerInfo { | ||||
|         using TextureConfig = Pica::TexturingRegs::TextureConfig; | ||||
| 
 | ||||
|         OGLSampler sampler; | ||||
| 
 | ||||
|         /// Creates the sampler object, initializing its state so that it's in sync with the
 | ||||
|         /// SamplerInfo struct.
 | ||||
|         void Create(); | ||||
|         /// Syncs the sampler object with the config, updating any necessary state.
 | ||||
|         void SyncWithConfig(const TextureConfig& config); | ||||
| 
 | ||||
|     private: | ||||
|         TextureConfig::TextureFilter mag_filter; | ||||
|         TextureConfig::TextureFilter min_filter; | ||||
|         TextureConfig::TextureFilter mip_filter; | ||||
|         TextureConfig::WrapMode wrap_s; | ||||
|         TextureConfig::WrapMode wrap_t; | ||||
|         u32 border_color; | ||||
|         u32 lod_min; | ||||
|         u32 lod_max; | ||||
|     }; | ||||
| 
 | ||||
|     /// Syncs the clip enabled status to match the PICA register
 | ||||
|     void SyncClipEnabled(); | ||||
| 
 | ||||
|  | @ -156,14 +134,13 @@ private: | |||
|     OpenGLState state; | ||||
|     GLuint default_texture; | ||||
|     TextureRuntime runtime; | ||||
|     VideoCore::RasterizerCache res_cache; | ||||
|     RasterizerCache res_cache; | ||||
|     std::unique_ptr<ShaderProgramManager> shader_program_manager; | ||||
| 
 | ||||
|     OGLVertexArray sw_vao; // VAO for software shader draw
 | ||||
|     OGLVertexArray hw_vao; // VAO for hardware shader / accelerate draw
 | ||||
|     std::array<bool, 16> hw_vao_enabled_attributes{}; | ||||
| 
 | ||||
|     std::array<SamplerInfo, 3> texture_samplers; | ||||
|     GLsizeiptr texture_buffer_size; | ||||
|     OGLStreamBuffer vertex_buffer; | ||||
|     OGLStreamBuffer uniform_buffer; | ||||
|  | @ -175,8 +152,6 @@ private: | |||
|     std::size_t uniform_size_aligned_vs; | ||||
|     std::size_t uniform_size_aligned_fs; | ||||
| 
 | ||||
|     SamplerInfo texture_cube_sampler; | ||||
| 
 | ||||
|     OGLTexture texture_buffer_lut_lf; | ||||
|     OGLTexture texture_buffer_lut_rg; | ||||
|     OGLTexture texture_buffer_lut_rgba; | ||||
|  |  | |||
							
								
								
									
										10
									
								
								src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| // Copyright 2023 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "video_core/rasterizer_cache/rasterizer_cache.h" | ||||
| #include "video_core/renderer_opengl/gl_texture_runtime.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| template class RasterizerCache<OpenGL::Traits>; | ||||
| } // namespace VideoCore
 | ||||
|  | @ -9,6 +9,7 @@ | |||
| #include "video_core/renderer_opengl/gl_driver.h" | ||||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
| #include "video_core/renderer_opengl/gl_texture_runtime.h" | ||||
| #include "video_core/renderer_opengl/pica_to_gl.h" | ||||
| 
 | ||||
| namespace OpenGL { | ||||
| 
 | ||||
|  | @ -16,6 +17,7 @@ namespace { | |||
| 
 | ||||
| using VideoCore::MapType; | ||||
| using VideoCore::PixelFormat; | ||||
| using VideoCore::SurfaceFlagBits; | ||||
| using VideoCore::SurfaceType; | ||||
| using VideoCore::TextureType; | ||||
| 
 | ||||
|  | @ -116,20 +118,11 @@ struct FramebufferInfo { | |||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& renderer) | ||||
|     : driver{driver_}, blit_helper{*this} { | ||||
|     : driver{driver_}, blit_helper{driver} { | ||||
|     for (std::size_t i = 0; i < draw_fbos.size(); ++i) { | ||||
|         draw_fbos[i].Create(); | ||||
|         read_fbos[i].Create(); | ||||
|     } | ||||
| 
 | ||||
|     auto add_reinterpreter = [this](PixelFormat dest, | ||||
|                                     std::unique_ptr<FormatReinterpreterBase>&& obj) { | ||||
|         const u32 dst_index = static_cast<u32>(dest); | ||||
|         return reinterpreters[dst_index].push_back(std::move(obj)); | ||||
|     }; | ||||
| 
 | ||||
|     add_reinterpreter(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>()); | ||||
|     add_reinterpreter(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>()); | ||||
| } | ||||
| 
 | ||||
| TextureRuntime::~TextureRuntime() = default; | ||||
|  | @ -241,14 +234,30 @@ Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params, | |||
|         .height = params.height, | ||||
|         .levels = params.levels, | ||||
|         .res_scale = params.res_scale, | ||||
|         .is_custom = is_custom, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) { | ||||
|     const auto prev_state = OpenGLState::GetCurState(); | ||||
| bool TextureRuntime::Reinterpret(Surface& source, Surface& dest, | ||||
|                                  const VideoCore::TextureBlit& blit) { | ||||
|     const PixelFormat src_format = source.pixel_format; | ||||
|     const PixelFormat dst_format = dest.pixel_format; | ||||
|     ASSERT_MSG(src_format != dst_format, "Reinterpretation with the same format is invalid"); | ||||
|     if (src_format == PixelFormat::D24S8 && dst_format == PixelFormat::RGBA8) { | ||||
|         blit_helper.ConvertDS24S8ToRGBA8(source, dest, blit); | ||||
|     } else if (src_format == PixelFormat::RGBA4 && dst_format == PixelFormat::RGB5A1) { | ||||
|         blit_helper.ConvertRGBA4ToRGB5A1(source, dest, blit); | ||||
|     } else { | ||||
|         LOG_WARNING(Render_OpenGL, "Unimplemented reinterpretation {} -> {}", | ||||
|                     VideoCore::PixelFormatAsString(src_format), | ||||
|                     VideoCore::PixelFormatAsString(dst_format)); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
|     // Setup scissor rectangle according to the clear rectangle
 | ||||
|     OpenGLState state; | ||||
| bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) { | ||||
|     OpenGLState state = OpenGLState::GetCurState(); | ||||
|     state.scissor.enabled = true; | ||||
|     state.scissor.x = clear.texture_rect.left; | ||||
|     state.scissor.y = clear.texture_rect.bottom; | ||||
|  | @ -257,42 +266,27 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea | |||
|     state.draw.draw_framebuffer = draw_fbos[FboIndex(surface.type)].handle; | ||||
|     state.Apply(); | ||||
| 
 | ||||
|     surface.Attach(GL_DRAW_FRAMEBUFFER, clear.texture_level, 0); | ||||
| 
 | ||||
|     switch (surface.type) { | ||||
|     case SurfaceType::Color: | ||||
|     case SurfaceType::Texture: | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | ||||
|                                surface.Handle(), clear.texture_level); | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||||
|                                0); | ||||
| 
 | ||||
|         state.color_mask.red_enabled = true; | ||||
|         state.color_mask.green_enabled = true; | ||||
|         state.color_mask.blue_enabled = true; | ||||
|         state.color_mask.alpha_enabled = true; | ||||
|         state.Apply(); | ||||
| 
 | ||||
|         glClearBufferfv(GL_COLOR, 0, clear.value.color.AsArray()); | ||||
|         break; | ||||
|     case SurfaceType::Depth: | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | ||||
|                                surface.Handle(), clear.texture_level); | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||
| 
 | ||||
|         state.depth.write_mask = GL_TRUE; | ||||
|         state.Apply(); | ||||
| 
 | ||||
|         glClearBufferfv(GL_DEPTH, 0, &clear.value.depth); | ||||
|         break; | ||||
|     case SurfaceType::DepthStencil: | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||||
|         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||||
|                                surface.Handle(), clear.texture_level); | ||||
| 
 | ||||
|         state.depth.write_mask = GL_TRUE; | ||||
|         state.stencil.write_mask = -1; | ||||
|         state.Apply(); | ||||
| 
 | ||||
|         glClearBufferfi(GL_DEPTH_STENCIL, 0, clear.value.depth, clear.value.stencil); | ||||
|         break; | ||||
|     default: | ||||
|  | @ -300,7 +294,6 @@ bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea | |||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     prev_state.Apply(); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
|  | @ -329,13 +322,12 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest, | |||
|     source.Attach(GL_READ_FRAMEBUFFER, blit.src_level, blit.src_layer); | ||||
|     dest.Attach(GL_DRAW_FRAMEBUFFER, blit.dst_level, blit.dst_layer); | ||||
| 
 | ||||
|     // TODO (wwylele): use GL_NEAREST for shadow map texture
 | ||||
|     // Note: shadow map is treated as RGBA8 format in PICA, as well as in the rasterizer cache, but
 | ||||
|     // doing linear intepolation componentwise would cause incorrect value. However, for a
 | ||||
|     // well-programmed game this code path should be rarely executed for shadow map with
 | ||||
|     // inconsistent scale.
 | ||||
|     // doing linear intepolation componentwise would cause incorrect value.
 | ||||
|     const GLbitfield buffer_mask = MakeBufferMask(source.type); | ||||
|     const GLenum filter = buffer_mask == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST; | ||||
|     const bool is_shadow_map = True(source.flags & SurfaceFlagBits::ShadowMap); | ||||
|     const GLenum filter = | ||||
|         buffer_mask == GL_COLOR_BUFFER_BIT && !is_shadow_map ? GL_LINEAR : GL_NEAREST; | ||||
|     glBlitFramebuffer(blit.src_rect.left, blit.src_rect.bottom, blit.src_rect.right, | ||||
|                       blit.src_rect.top, blit.dst_rect.left, blit.dst_rect.bottom, | ||||
|                       blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter); | ||||
|  | @ -359,11 +351,6 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations( | ||||
|     PixelFormat dest_format) const { | ||||
|     return reinterpreters[static_cast<u32>(dest_format)]; | ||||
| } | ||||
| 
 | ||||
| Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params) | ||||
|     : SurfaceBase{params}, driver{&runtime_.GetDriver()}, runtime{&runtime_} { | ||||
|     if (pixel_format == PixelFormat::Invalid) { | ||||
|  | @ -422,15 +409,19 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) { | |||
|     glActiveTexture(GL_TEXTURE0); | ||||
|     glPixelStorei(GL_UNPACK_ROW_LENGTH, width); | ||||
| 
 | ||||
|     glBindTexture(GL_TEXTURE_2D, Handle(0)); | ||||
|     if (VideoCore::IsCustomFormatCompressed(custom_format)) { | ||||
|         const GLsizei image_size = static_cast<GLsizei>(color->data.size()); | ||||
|         glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, | ||||
|                                   image_size, color->data.data()); | ||||
|     } else { | ||||
|         glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type, | ||||
|                         color->data.data()); | ||||
|     } | ||||
|     const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) { | ||||
|         glBindTexture(GL_TEXTURE_2D, Handle(index)); | ||||
|         if (VideoCore::IsCustomFormatCompressed(custom_format)) { | ||||
|             const GLsizei image_size = static_cast<GLsizei>(texture->data.size()); | ||||
|             glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, | ||||
|                                       image_size, texture->data.data()); | ||||
|         } else { | ||||
|             glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type, | ||||
|                             texture->data.data()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     upload(0, color); | ||||
| 
 | ||||
|     const VideoCore::TextureBlit blit = { | ||||
|         .src_rect = filter_rect, | ||||
|  | @ -444,15 +435,7 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) { | |||
|         if (!texture) { | ||||
|             continue; | ||||
|         } | ||||
|         glBindTexture(GL_TEXTURE_2D, Handle(i + 1)); | ||||
|         if (VideoCore::IsCustomFormatCompressed(custom_format)) { | ||||
|             const GLsizei image_size = static_cast<GLsizei>(texture->data.size()); | ||||
|             glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, | ||||
|                                       image_size, texture->data.data()); | ||||
|         } else { | ||||
|             glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type, | ||||
|                             texture->data.data()); | ||||
|         } | ||||
|         upload(i + 1, texture); | ||||
|     } | ||||
| 
 | ||||
|     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||
|  | @ -572,7 +555,6 @@ bool Surface::Swap(const VideoCore::Material* mat) { | |||
|               GetScaledWidth(), GetScaledHeight(), VideoCore::PixelFormatAsString(pixel_format), | ||||
|               addr, width, height, VideoCore::CustomPixelFormatAsString(format)); | ||||
| 
 | ||||
|     is_custom = true; | ||||
|     custom_format = format; | ||||
|     material = mat; | ||||
| 
 | ||||
|  | @ -614,13 +596,13 @@ HostTextureTag Surface::MakeTag() const noexcept { | |||
|         .res_scale = alloc.res_scale, | ||||
|         .tuple = alloc.tuple, | ||||
|         .type = texture_type, | ||||
|         .is_custom = is_custom, | ||||
|         .is_custom = alloc.is_custom, | ||||
|         .has_normal = HasNormalMap(), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level, | ||||
|                          Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||
| Framebuffer::Framebuffer(TextureRuntime& runtime, const Surface* color, u32 color_level, | ||||
|                          const Surface* depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||
|                          Common::Rectangle<u32> surfaces_rect) | ||||
|     : VideoCore::FramebufferBase{regs,          color,       color_level, | ||||
|                                  depth_stencil, depth_level, surfaces_rect} { | ||||
|  | @ -692,4 +674,30 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo | |||
| 
 | ||||
| Framebuffer::~Framebuffer() = default; | ||||
| 
 | ||||
| Sampler::Sampler(TextureRuntime&, VideoCore::SamplerParams params) { | ||||
|     const GLenum mag_filter = PicaToGL::TextureMagFilterMode(params.mag_filter); | ||||
|     const GLenum min_filter = PicaToGL::TextureMinFilterMode(params.min_filter, params.mip_filter); | ||||
|     const GLenum wrap_s = PicaToGL::WrapMode(params.wrap_s); | ||||
|     const GLenum wrap_t = PicaToGL::WrapMode(params.wrap_t); | ||||
|     const Common::Vec4f gl_color = PicaToGL::ColorRGBA8(params.border_color); | ||||
|     const float lod_min = params.lod_min; | ||||
|     const float lod_max = params.lod_max; | ||||
| 
 | ||||
|     sampler.Create(); | ||||
| 
 | ||||
|     const GLuint handle = sampler.handle; | ||||
|     glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag_filter); | ||||
|     glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min_filter); | ||||
| 
 | ||||
|     glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, wrap_s); | ||||
|     glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, wrap_t); | ||||
| 
 | ||||
|     glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, gl_color.AsArray()); | ||||
| 
 | ||||
|     glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, lod_min); | ||||
|     glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, lod_max); | ||||
| } | ||||
| 
 | ||||
| Sampler::~Sampler() = default; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
|  | @ -5,9 +5,8 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "video_core/rasterizer_cache/framebuffer_base.h" | ||||
| #include "video_core/rasterizer_cache/surface_base.h" | ||||
| #include "video_core/rasterizer_cache/rasterizer_cache_base.h" | ||||
| #include "video_core/renderer_opengl/gl_blit_helper.h" | ||||
| #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||
| 
 | ||||
| namespace VideoCore { | ||||
| struct Material; | ||||
|  | @ -60,6 +59,7 @@ struct Allocation { | |||
|     u32 height; | ||||
|     u32 levels; | ||||
|     u32 res_scale; | ||||
|     bool is_custom; | ||||
| 
 | ||||
|     operator bool() const noexcept { | ||||
|         return textures[0].handle; | ||||
|  | @ -76,7 +76,6 @@ class Driver; | |||
| class TextureRuntime { | ||||
|     friend class Surface; | ||||
|     friend class Framebuffer; | ||||
|     friend class BlitHelper; | ||||
| 
 | ||||
| public: | ||||
|     explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer); | ||||
|  | @ -95,12 +94,8 @@ public: | |||
|     const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const; | ||||
|     const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format); | ||||
| 
 | ||||
|     /// Takes back ownership of the allocation for recycling
 | ||||
|     void Recycle(const HostTextureTag tag, Allocation&& alloc); | ||||
| 
 | ||||
|     /// Allocates a texture with the specified dimentions and format
 | ||||
|     Allocation Allocate(const VideoCore::SurfaceParams& params, | ||||
|                         const VideoCore::Material* material = nullptr); | ||||
|     /// Attempts to reinterpret a rectangle of source to another rectangle of dest
 | ||||
|     bool Reinterpret(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
| 
 | ||||
|     /// Fills the rectangle of the texture with the clear value provided
 | ||||
|     bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear); | ||||
|  | @ -114,10 +109,14 @@ public: | |||
|     /// Generates mipmaps for all the available levels of the texture
 | ||||
|     void GenerateMipmaps(Surface& surface); | ||||
| 
 | ||||
|     /// Returns all source formats that support reinterpretation to the dest format
 | ||||
|     const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const; | ||||
| 
 | ||||
| private: | ||||
|     /// Takes back ownership of the allocation for recycling
 | ||||
|     void Recycle(const HostTextureTag tag, Allocation&& alloc); | ||||
| 
 | ||||
|     /// Allocates a texture with the specified dimentions and format
 | ||||
|     Allocation Allocate(const VideoCore::SurfaceParams& params, | ||||
|                         const VideoCore::Material* material = nullptr); | ||||
| 
 | ||||
|     /// Returns the OpenGL driver class
 | ||||
|     const Driver& GetDriver() const { | ||||
|         return driver; | ||||
|  | @ -127,7 +126,6 @@ private: | |||
|     const Driver& driver; | ||||
|     BlitHelper blit_helper; | ||||
|     std::vector<u8> staging_buffer; | ||||
|     std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters; | ||||
|     std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> alloc_cache; | ||||
|     std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache; | ||||
|     std::array<OGLFramebuffer, 3> draw_fbos; | ||||
|  | @ -145,24 +143,14 @@ public: | |||
|     Surface(Surface&& o) noexcept = default; | ||||
|     Surface& operator=(Surface&& o) noexcept = default; | ||||
| 
 | ||||
|     /// Returns the surface image handle at the provided index.
 | ||||
|     GLuint Handle(u32 index = 1) const noexcept { | ||||
|     [[nodiscard]] GLuint Handle(u32 index = 1) const noexcept { | ||||
|         return alloc.handles[index]; | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the tuple of the surface allocation.
 | ||||
|     const FormatTuple& Tuple() const noexcept { | ||||
|     [[nodiscard]] const FormatTuple& Tuple() const noexcept { | ||||
|         return alloc.tuple; | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the extent of the underlying surface allocation
 | ||||
|     VideoCore::Extent Extent() const noexcept { | ||||
|         return { | ||||
|             .width = alloc.width, | ||||
|             .height = alloc.height, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /// Uploads pixel data in staging to a rectangle region of the surface texture
 | ||||
|     void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging); | ||||
| 
 | ||||
|  | @ -201,8 +189,8 @@ private: | |||
| 
 | ||||
| class Framebuffer : public VideoCore::FramebufferBase { | ||||
| public: | ||||
|     explicit Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level, | ||||
|                          Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||
|     explicit Framebuffer(TextureRuntime& runtime, const Surface* color, u32 color_level, | ||||
|                          const Surface* depth_stencil, u32 depth_level, const Pica::Regs& regs, | ||||
|                          Common::Rectangle<u32> surfaces_rect); | ||||
|     ~Framebuffer(); | ||||
| 
 | ||||
|  | @ -223,4 +211,32 @@ private: | |||
|     GLuint handle{}; | ||||
| }; | ||||
| 
 | ||||
| class Sampler { | ||||
| public: | ||||
|     explicit Sampler(TextureRuntime&, VideoCore::SamplerParams params); | ||||
|     ~Sampler(); | ||||
| 
 | ||||
|     Sampler(const Sampler&) = delete; | ||||
|     Sampler& operator=(const Sampler&) = delete; | ||||
| 
 | ||||
|     Sampler(Sampler&&) = default; | ||||
|     Sampler& operator=(Sampler&&) = default; | ||||
| 
 | ||||
|     [[nodiscard]] GLuint Handle() const noexcept { | ||||
|         return sampler.handle; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     OGLSampler sampler; | ||||
| }; | ||||
| 
 | ||||
| struct Traits { | ||||
|     using Runtime = OpenGL::TextureRuntime; | ||||
|     using Sampler = OpenGL::Sampler; | ||||
|     using Surface = OpenGL::Surface; | ||||
|     using Framebuffer = OpenGL::Framebuffer; | ||||
| }; | ||||
| 
 | ||||
| using RasterizerCache = VideoCore::RasterizerCache<Traits>; | ||||
| 
 | ||||
| } // namespace OpenGL
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue