video_core/renderer_opengl/gl_rasterizer_cache: Create Format Reinterpretation Framework (#5170)
* video_core/renderer_opengl/gl_rasterizer_cache: Create Format Reinterpretation Framework Adds RGBA4 -> RGB5A1 reinterpretation commonly used by virtual console If no matching surface can be found, ValidateSurface checks for a surface in the cache which is reinterpretable to the requested format. If that fails, the cache is checked for any surface with a matching bit-width. If one is found, the region is flushed. If not, the region is checked against dirty_regions to see if it was created entirely on the GPU. If not, then the surface is flushed. Co-Authored-By: James Rowe <jroweboy@users.noreply.github.com> Co-Authored-By: Ben <b3n30@users.noreply.github.com> temporary change to avoid merge conflicts with video dumping * re-add D24S8->RGBA8 res_scale hack * adress review comments * fix dirty region check * check for surfaces with invalid pixel format, and break logic into separate functions
This commit is contained in:
		| @@ -63,6 +63,9 @@ add_library(video_core STATIC | |||||||
|     renderer_opengl/texture_filters/texture_filterer.h |     renderer_opengl/texture_filters/texture_filterer.h | ||||||
|     renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp |     renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp | ||||||
|     renderer_opengl/texture_filters/xbrz/xbrz_freescale.h |     renderer_opengl/texture_filters/xbrz/xbrz_freescale.h | ||||||
|  |     #temporary, move these back in alphabetical order before merging | ||||||
|  |     renderer_opengl/gl_format_reinterpreter.cpp | ||||||
|  |     renderer_opengl/gl_format_reinterpreter.h | ||||||
|     shader/debug_data.h |     shader/debug_data.h | ||||||
|     shader/shader.cpp |     shader/shader.cpp | ||||||
|     shader/shader.h |     shader/shader.h | ||||||
|   | |||||||
							
								
								
									
										238
									
								
								src/video_core/renderer_opengl/gl_format_reinterpreter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/video_core/renderer_opengl/gl_format_reinterpreter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | |||||||
|  | // Copyright 2020 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/scope_exit.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_state.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_vars.h" | ||||||
|  | #include "video_core/renderer_opengl/texture_filters/texture_filterer.h" | ||||||
|  |  | ||||||
|  | namespace OpenGL { | ||||||
|  |  | ||||||
|  | using PixelFormat = SurfaceParams::PixelFormat; | ||||||
|  |  | ||||||
|  | class RGBA4toRGB5A1 final : public FormatReinterpreterBase { | ||||||
|  | public: | ||||||
|  |     RGBA4toRGB5A1() { | ||||||
|  |         constexpr std::string_view vs_source = R"( | ||||||
|  | out vec2 dst_coord; | ||||||
|  |  | ||||||
|  | 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)); | ||||||
|  |  | ||||||
|  | void main() { | ||||||
|  |     gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); | ||||||
|  |     dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size); | ||||||
|  | } | ||||||
|  | )"; | ||||||
|  |  | ||||||
|  |         constexpr std::string_view fs_source = R"( | ||||||
|  | in mediump vec2 dst_coord; | ||||||
|  |  | ||||||
|  | out lowp vec4 frag_color; | ||||||
|  |  | ||||||
|  | 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)); | ||||||
|  |     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); | ||||||
|  | } | ||||||
|  | )"; | ||||||
|  |  | ||||||
|  |         program.Create(vs_source.data(), fs_source.data()); | ||||||
|  |         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 Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle, | ||||||
|  |                      GLuint dst_tex, const Common::Rectangle<u32>& dst_rect, | ||||||
|  |                      GLuint draw_fb_handle) override { | ||||||
|  |         OpenGLState prev_state = OpenGLState::GetCurState(); | ||||||
|  |         SCOPE_EXIT({ prev_state.Apply(); }); | ||||||
|  |  | ||||||
|  |         OpenGLState state; | ||||||
|  |         state.texture_units[0].texture_2d = src_tex; | ||||||
|  |         state.draw.draw_framebuffer = draw_fb_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, dst_tex, | ||||||
|  |                                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); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     OGLProgram program; | ||||||
|  |     GLint dst_size_loc{-1}, src_size_loc{-1}, src_offset_loc{-1}; | ||||||
|  |     OGLVertexArray vao; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class PixelBufferD24S8toABGR final : public FormatReinterpreterBase { | ||||||
|  | public: | ||||||
|  |     PixelBufferD24S8toABGR() { | ||||||
|  |         attributeless_vao.Create(); | ||||||
|  |         d24s8_abgr_buffer.Create(); | ||||||
|  |         d24s8_abgr_buffer_size = 0; | ||||||
|  |  | ||||||
|  |         constexpr std::string_view vs_source = R"( | ||||||
|  | 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)); | ||||||
|  | void main() { | ||||||
|  |     gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); | ||||||
|  | } | ||||||
|  | )"; | ||||||
|  |  | ||||||
|  |         std::string fs_source = GLES ? fragment_shader_precision_OES : ""; | ||||||
|  |         fs_source += R"( | ||||||
|  | uniform samplerBuffer tbo; | ||||||
|  | uniform vec2 tbo_size; | ||||||
|  | uniform vec4 viewport; | ||||||
|  |  | ||||||
|  | out vec4 color; | ||||||
|  |  | ||||||
|  | void main() { | ||||||
|  |     vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw; | ||||||
|  |     int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x); | ||||||
|  |     color = texelFetch(tbo, tbo_offset).rabg; | ||||||
|  | } | ||||||
|  | )"; | ||||||
|  |         d24s8_abgr_shader.Create(vs_source.data(), fs_source.c_str()); | ||||||
|  |  | ||||||
|  |         OpenGLState state = OpenGLState::GetCurState(); | ||||||
|  |         GLuint old_program = state.draw.shader_program; | ||||||
|  |         state.draw.shader_program = d24s8_abgr_shader.handle; | ||||||
|  |         state.Apply(); | ||||||
|  |  | ||||||
|  |         GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo"); | ||||||
|  |         ASSERT(tbo_u_id != -1); | ||||||
|  |         glUniform1i(tbo_u_id, 0); | ||||||
|  |  | ||||||
|  |         state.draw.shader_program = old_program; | ||||||
|  |         state.Apply(); | ||||||
|  |  | ||||||
|  |         d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size"); | ||||||
|  |         ASSERT(d24s8_abgr_tbo_size_u_id != -1); | ||||||
|  |         d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport"); | ||||||
|  |         ASSERT(d24s8_abgr_viewport_u_id != -1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ~PixelBufferD24S8toABGR() {} | ||||||
|  |  | ||||||
|  |     void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle, | ||||||
|  |                      GLuint dst_tex, const Common::Rectangle<u32>& dst_rect, | ||||||
|  |                      GLuint draw_fb_handle) override { | ||||||
|  |         OpenGLState prev_state = OpenGLState::GetCurState(); | ||||||
|  |         SCOPE_EXIT({ prev_state.Apply(); }); | ||||||
|  |  | ||||||
|  |         OpenGLState state; | ||||||
|  |         state.draw.read_framebuffer = read_fb_handle; | ||||||
|  |         state.draw.draw_framebuffer = draw_fb_handle; | ||||||
|  |         state.Apply(); | ||||||
|  |  | ||||||
|  |         glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); | ||||||
|  |  | ||||||
|  |         GLsizeiptr target_pbo_size = | ||||||
|  |             static_cast<GLsizeiptr>(src_rect.GetWidth()) * src_rect.GetHeight() * 4; | ||||||
|  |         if (target_pbo_size > d24s8_abgr_buffer_size) { | ||||||
|  |             d24s8_abgr_buffer_size = target_pbo_size * 2; | ||||||
|  |             glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||||||
|  |         glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||||||
|  |                                src_tex, 0); | ||||||
|  |         glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom), | ||||||
|  |                      static_cast<GLsizei>(src_rect.GetWidth()), | ||||||
|  |                      static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, | ||||||
|  |                      GL_UNSIGNED_INT_24_8, 0); | ||||||
|  |  | ||||||
|  |         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); | ||||||
|  |  | ||||||
|  |         // PBO now contains src_tex in RABG format | ||||||
|  |         state.draw.shader_program = d24s8_abgr_shader.handle; | ||||||
|  |         state.draw.vertex_array = attributeless_vao.handle; | ||||||
|  |         state.viewport.x = static_cast<GLint>(dst_rect.left); | ||||||
|  |         state.viewport.y = static_cast<GLint>(dst_rect.bottom); | ||||||
|  |         state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth()); | ||||||
|  |         state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight()); | ||||||
|  |         state.Apply(); | ||||||
|  |  | ||||||
|  |         OGLTexture tbo; | ||||||
|  |         tbo.Create(); | ||||||
|  |         glActiveTexture(GL_TEXTURE0); | ||||||
|  |         glBindTexture(GL_TEXTURE_BUFFER, tbo.handle); | ||||||
|  |         glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle); | ||||||
|  |  | ||||||
|  |         glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()), | ||||||
|  |                     static_cast<GLfloat>(src_rect.GetHeight())); | ||||||
|  |         glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x), | ||||||
|  |                     static_cast<GLfloat>(state.viewport.y), | ||||||
|  |                     static_cast<GLfloat>(state.viewport.width), | ||||||
|  |                     static_cast<GLfloat>(state.viewport.height)); | ||||||
|  |  | ||||||
|  |         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, | ||||||
|  |                                0); | ||||||
|  |         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||||||
|  |                                0); | ||||||
|  |         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||||
|  |  | ||||||
|  |         glBindTexture(GL_TEXTURE_BUFFER, 0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     OGLVertexArray attributeless_vao; | ||||||
|  |     OGLBuffer d24s8_abgr_buffer; | ||||||
|  |     GLsizeiptr d24s8_abgr_buffer_size; | ||||||
|  |     OGLProgram d24s8_abgr_shader; | ||||||
|  |     GLint d24s8_abgr_tbo_size_u_id; | ||||||
|  |     GLint d24s8_abgr_viewport_u_id; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | FormatReinterpreterOpenGL::FormatReinterpreterOpenGL() { | ||||||
|  |     reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8}, | ||||||
|  |                            std::make_unique<PixelBufferD24S8toABGR>()); | ||||||
|  |     reinterpreters.emplace(PixelFormatPair{PixelFormat::RGB5A1, PixelFormat::RGBA4}, | ||||||
|  |                            std::make_unique<RGBA4toRGB5A1>()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default; | ||||||
|  |  | ||||||
|  | std::pair<FormatReinterpreterOpenGL::ReinterpreterMap::iterator, | ||||||
|  |           FormatReinterpreterOpenGL::ReinterpreterMap::iterator> | ||||||
|  | FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) { | ||||||
|  |     return reinterpreters.equal_range(dst_format); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace OpenGL | ||||||
							
								
								
									
										62
									
								
								src/video_core/renderer_opengl/gl_format_reinterpreter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/video_core/renderer_opengl/gl_format_reinterpreter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | // Copyright 2020 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <glad/glad.h> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/math_util.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_surface_params.h" | ||||||
|  |  | ||||||
|  | namespace OpenGL { | ||||||
|  |  | ||||||
|  | class RasterizerCacheOpenGL; | ||||||
|  |  | ||||||
|  | struct PixelFormatPair { | ||||||
|  |     const SurfaceParams::PixelFormat dst_format, src_format; | ||||||
|  |     struct less { | ||||||
|  |         using is_transparent = void; | ||||||
|  |         constexpr bool operator()(OpenGL::PixelFormatPair lhs, OpenGL::PixelFormatPair rhs) const { | ||||||
|  |             return std::tie(lhs.dst_format, lhs.src_format) < | ||||||
|  |                    std::tie(rhs.dst_format, rhs.src_format); | ||||||
|  |         } | ||||||
|  |         constexpr bool operator()(OpenGL::SurfaceParams::PixelFormat lhs, | ||||||
|  |                                   OpenGL::PixelFormatPair rhs) const { | ||||||
|  |             return lhs < rhs.dst_format; | ||||||
|  |         } | ||||||
|  |         constexpr bool operator()(OpenGL::PixelFormatPair lhs, | ||||||
|  |                                   OpenGL::SurfaceParams::PixelFormat rhs) const { | ||||||
|  |             return lhs.dst_format < rhs; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class FormatReinterpreterBase { | ||||||
|  | public: | ||||||
|  |     virtual ~FormatReinterpreterBase() = default; | ||||||
|  |  | ||||||
|  |     virtual void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, | ||||||
|  |                              GLuint read_fb_handle, GLuint dst_tex, | ||||||
|  |                              const Common::Rectangle<u32>& dst_rect, GLuint draw_fb_handle) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class FormatReinterpreterOpenGL : NonCopyable { | ||||||
|  |     using ReinterpreterMap = | ||||||
|  |         std::map<PixelFormatPair, std::unique_ptr<FormatReinterpreterBase>, PixelFormatPair::less>; | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |     explicit FormatReinterpreterOpenGL(); | ||||||
|  |     ~FormatReinterpreterOpenGL(); | ||||||
|  |  | ||||||
|  |     std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations( | ||||||
|  |         SurfaceParams::PixelFormat dst_format); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     ReinterpreterMap reinterpreters; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace OpenGL | ||||||
| @@ -32,6 +32,7 @@ | |||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "video_core/pica_state.h" | #include "video_core/pica_state.h" | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_format_reinterpreter.h" | ||||||
| #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| #include "video_core/renderer_opengl/gl_vars.h" | #include "video_core/renderer_opengl/gl_vars.h" | ||||||
| @@ -1063,54 +1064,10 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() { | |||||||
|     resolution_scale_factor = VideoCore::GetResolutionScaleFactor(); |     resolution_scale_factor = VideoCore::GetResolutionScaleFactor(); | ||||||
|     texture_filterer = std::make_unique<TextureFilterer>(Settings::values.texture_filter_name, |     texture_filterer = std::make_unique<TextureFilterer>(Settings::values.texture_filter_name, | ||||||
|                                                          resolution_scale_factor); |                                                          resolution_scale_factor); | ||||||
|  |     format_reinterpreter = std::make_unique<FormatReinterpreterOpenGL>(); | ||||||
|  |  | ||||||
|     read_framebuffer.Create(); |     read_framebuffer.Create(); | ||||||
|     draw_framebuffer.Create(); |     draw_framebuffer.Create(); | ||||||
|  |  | ||||||
|     attributeless_vao.Create(); |  | ||||||
|  |  | ||||||
|     d24s8_abgr_buffer.Create(); |  | ||||||
|     d24s8_abgr_buffer_size = 0; |  | ||||||
|  |  | ||||||
|     std::string vs_source = R"( |  | ||||||
| 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)); |  | ||||||
| void main() { |  | ||||||
|     gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); |  | ||||||
| } |  | ||||||
| )"; |  | ||||||
|  |  | ||||||
|     std::string fs_source = GLES ? fragment_shader_precision_OES : ""; |  | ||||||
|     fs_source += R"( |  | ||||||
| uniform samplerBuffer tbo; |  | ||||||
| uniform vec2 tbo_size; |  | ||||||
| uniform vec4 viewport; |  | ||||||
|  |  | ||||||
| out vec4 color; |  | ||||||
|  |  | ||||||
| void main() { |  | ||||||
|     vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw; |  | ||||||
|     int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x); |  | ||||||
|     color = texelFetch(tbo, tbo_offset).rabg; |  | ||||||
| } |  | ||||||
| )"; |  | ||||||
|     d24s8_abgr_shader.Create(vs_source.c_str(), fs_source.c_str()); |  | ||||||
|  |  | ||||||
|     OpenGLState state = OpenGLState::GetCurState(); |  | ||||||
|     GLuint old_program = state.draw.shader_program; |  | ||||||
|     state.draw.shader_program = d24s8_abgr_shader.handle; |  | ||||||
|     state.Apply(); |  | ||||||
|  |  | ||||||
|     GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo"); |  | ||||||
|     ASSERT(tbo_u_id != -1); |  | ||||||
|     glUniform1i(tbo_u_id, 0); |  | ||||||
|  |  | ||||||
|     state.draw.shader_program = old_program; |  | ||||||
|     state.Apply(); |  | ||||||
|  |  | ||||||
|     d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size"); |  | ||||||
|     ASSERT(d24s8_abgr_tbo_size_u_id != -1); |  | ||||||
|     d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport"); |  | ||||||
|     ASSERT(d24s8_abgr_viewport_u_id != -1); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { | RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { | ||||||
| @@ -1136,64 +1093,6 @@ bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, | |||||||
|                         draw_framebuffer.handle); |                         draw_framebuffer.handle); | ||||||
| } | } | ||||||
|  |  | ||||||
| void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex, |  | ||||||
|                                                const Common::Rectangle<u32>& src_rect, |  | ||||||
|                                                GLuint dst_tex, |  | ||||||
|                                                const Common::Rectangle<u32>& dst_rect) { |  | ||||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); |  | ||||||
|     SCOPE_EXIT({ prev_state.Apply(); }); |  | ||||||
|  |  | ||||||
|     OpenGLState state; |  | ||||||
|     state.draw.read_framebuffer = read_framebuffer.handle; |  | ||||||
|     state.draw.draw_framebuffer = draw_framebuffer.handle; |  | ||||||
|     state.Apply(); |  | ||||||
|  |  | ||||||
|     glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); |  | ||||||
|  |  | ||||||
|     GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4; |  | ||||||
|     if (target_pbo_size > d24s8_abgr_buffer_size) { |  | ||||||
|         d24s8_abgr_buffer_size = target_pbo_size * 2; |  | ||||||
|         glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); |  | ||||||
|     glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, |  | ||||||
|                            0); |  | ||||||
|     glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom), |  | ||||||
|                  static_cast<GLsizei>(src_rect.GetWidth()), |  | ||||||
|                  static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, |  | ||||||
|                  0); |  | ||||||
|  |  | ||||||
|     glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); |  | ||||||
|  |  | ||||||
|     // PBO now contains src_tex in RABG format |  | ||||||
|     state.draw.shader_program = d24s8_abgr_shader.handle; |  | ||||||
|     state.draw.vertex_array = attributeless_vao.handle; |  | ||||||
|     state.viewport.x = static_cast<GLint>(dst_rect.left); |  | ||||||
|     state.viewport.y = static_cast<GLint>(dst_rect.bottom); |  | ||||||
|     state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth()); |  | ||||||
|     state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight()); |  | ||||||
|     state.Apply(); |  | ||||||
|  |  | ||||||
|     OGLTexture tbo; |  | ||||||
|     tbo.Create(); |  | ||||||
|     glActiveTexture(GL_TEXTURE0); |  | ||||||
|     glBindTexture(GL_TEXTURE_BUFFER, tbo.handle); |  | ||||||
|     glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle); |  | ||||||
|  |  | ||||||
|     glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()), |  | ||||||
|                 static_cast<GLfloat>(src_rect.GetHeight())); |  | ||||||
|     glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x), |  | ||||||
|                 static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width), |  | ||||||
|                 static_cast<GLfloat>(state.viewport.height)); |  | ||||||
|  |  | ||||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); |  | ||||||
|     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); |  | ||||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |  | ||||||
|  |  | ||||||
|     glBindTexture(GL_TEXTURE_BUFFER, 0); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, | Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, | ||||||
|                                           bool load_if_create) { |                                           bool load_if_create) { | ||||||
|     if (params.addr == 0 || params.height * params.width == 0) { |     if (params.addr == 0 || params.height * params.width == 0) { | ||||||
| @@ -1721,9 +1620,15 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     auto validate_regions = surface->invalid_regions & validate_interval; | ||||||
|  |     auto notify_validated = [&](SurfaceInterval interval) { | ||||||
|  |         surface->invalid_regions.erase(interval); | ||||||
|  |         validate_regions.erase(interval); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     while (true) { |     while (true) { | ||||||
|         const auto it = surface->invalid_regions.find(validate_interval); |         const auto it = validate_regions.begin(); | ||||||
|         if (it == surface->invalid_regions.end()) |         if (it == validate_regions.end()) | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         const auto interval = *it & validate_interval; |         const auto interval = *it & validate_interval; | ||||||
| @@ -1735,27 +1640,27 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, | |||||||
|         if (copy_surface != nullptr) { |         if (copy_surface != nullptr) { | ||||||
|             SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); |             SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); | ||||||
|             CopySurface(copy_surface, surface, copy_interval); |             CopySurface(copy_surface, surface, copy_interval); | ||||||
|             surface->invalid_regions.erase(copy_interval); |             notify_validated(copy_interval); | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // D24S8 to RGBA8 |         // Try to find surface in cache with different format | ||||||
|         if (surface->pixel_format == PixelFormat::RGBA8) { |         // that can can be reinterpreted to the requested format. | ||||||
|             params.pixel_format = PixelFormat::D24S8; |         if (ValidateByReinterpretation(surface, params, interval)) { | ||||||
|             Surface reinterpret_surface = |             notify_validated(interval); | ||||||
|                 FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); |             continue; | ||||||
|             if (reinterpret_surface != nullptr) { |         } | ||||||
|                 ASSERT(reinterpret_surface->pixel_format == PixelFormat::D24S8); |         // Could not find a matching reinterpreter, check if we need to implement a | ||||||
|  |         // reinterpreter | ||||||
|                 SurfaceInterval convert_interval = params.GetCopyableInterval(reinterpret_surface); |         if (NoUnimplementedReinterpretations(surface, params, interval) && | ||||||
|                 SurfaceParams convert_params = surface->FromInterval(convert_interval); |             !IntervalHasInvalidPixelFormat(params, interval)) { | ||||||
|                 auto src_rect = reinterpret_surface->GetScaledSubRect(convert_params); |             // No surfaces were found in the cache that had a matching bit-width. | ||||||
|                 auto dest_rect = surface->GetScaledSubRect(convert_params); |             // If the region was created entirely on the GPU, | ||||||
|  |             // assume it was a developer mistake and skip flushing. | ||||||
|                 ConvertD24S8toABGR(reinterpret_surface->texture.handle, src_rect, |             if (boost::icl::contains(dirty_regions, interval)) { | ||||||
|                                    surface->texture.handle, dest_rect); |                 LOG_DEBUG(Render_OpenGL, "Region created fully on GPU and reinterpretation is " | ||||||
|  |                                          "invalid. Skipping validation"); | ||||||
|                 surface->invalid_regions.erase(convert_interval); |                 validate_regions.erase(interval); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -1765,10 +1670,103 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, | |||||||
|         surface->LoadGLBuffer(params.addr, params.end); |         surface->LoadGLBuffer(params.addr, params.end); | ||||||
|         surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, |         surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, | ||||||
|                                  draw_framebuffer.handle); |                                  draw_framebuffer.handle); | ||||||
|         surface->invalid_regions.erase(params.GetInterval()); |         notify_validated(params.GetInterval()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool RasterizerCacheOpenGL::NoUnimplementedReinterpretations(const Surface& surface, | ||||||
|  |                                                              SurfaceParams& params, | ||||||
|  |                                                              const SurfaceInterval& interval) { | ||||||
|  |     static constexpr std::array<PixelFormat, 17> all_formats{ | ||||||
|  |         PixelFormat::RGBA8, PixelFormat::RGB8,   PixelFormat::RGB5A1, PixelFormat::RGB565, | ||||||
|  |         PixelFormat::RGBA4, PixelFormat::IA8,    PixelFormat::RG8,    PixelFormat::I8, | ||||||
|  |         PixelFormat::A8,    PixelFormat::IA4,    PixelFormat::I4,     PixelFormat::A4, | ||||||
|  |         PixelFormat::ETC1,  PixelFormat::ETC1A4, PixelFormat::D16,    PixelFormat::D24, | ||||||
|  |         PixelFormat::D24S8, | ||||||
|  |     }; | ||||||
|  |     bool implemented = true; | ||||||
|  |     for (PixelFormat format : all_formats) { | ||||||
|  |         if (SurfaceParams::GetFormatBpp(format) == surface->GetFormatBpp()) { | ||||||
|  |             params.pixel_format = format; | ||||||
|  |             // This could potentially be expensive, | ||||||
|  |             // although experimentally it hasn't been too bad | ||||||
|  |             Surface test_surface = | ||||||
|  |                 FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); | ||||||
|  |             if (test_surface != nullptr) { | ||||||
|  |                 LOG_WARNING(Render_OpenGL, "Missing pixel_format reinterpreter: {} -> {}", | ||||||
|  |                             SurfaceParams::PixelFormatAsString(format), | ||||||
|  |                             SurfaceParams::PixelFormatAsString(surface->pixel_format)); | ||||||
|  |                 implemented = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return implemented; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool RasterizerCacheOpenGL::IntervalHasInvalidPixelFormat(SurfaceParams& params, | ||||||
|  |                                                           const SurfaceInterval& interval) { | ||||||
|  |     params.pixel_format = PixelFormat::Invalid; | ||||||
|  |     for (const auto& set : RangeFromInterval(surface_cache, interval)) | ||||||
|  |         for (const auto& surface : set.second) | ||||||
|  |             if (surface->pixel_format == PixelFormat::Invalid) { | ||||||
|  |                 LOG_WARNING(Render_OpenGL, "Surface found with invalid pixel format"); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface, | ||||||
|  |                                                        SurfaceParams& params, | ||||||
|  |                                                        const SurfaceInterval& interval) { | ||||||
|  |     auto [cvt_begin, cvt_end] = | ||||||
|  |         format_reinterpreter->GetPossibleReinterpretations(surface->pixel_format); | ||||||
|  |     for (auto reinterpreter = cvt_begin; reinterpreter != cvt_end; ++reinterpreter) { | ||||||
|  |         PixelFormat format = reinterpreter->first.src_format; | ||||||
|  |         params.pixel_format = format; | ||||||
|  |         Surface reinterpret_surface = | ||||||
|  |             FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); | ||||||
|  |  | ||||||
|  |         if (reinterpret_surface != nullptr) { | ||||||
|  |             SurfaceInterval reinterpret_interval = params.GetCopyableInterval(reinterpret_surface); | ||||||
|  |             SurfaceParams reinterpret_params = surface->FromInterval(reinterpret_interval); | ||||||
|  |             auto src_rect = reinterpret_surface->GetScaledSubRect(reinterpret_params); | ||||||
|  |             auto dest_rect = surface->GetScaledSubRect(reinterpret_params); | ||||||
|  |  | ||||||
|  |             if (!texture_filterer->IsNull() && reinterpret_surface->res_scale == 1 && | ||||||
|  |                 surface->res_scale == resolution_scale_factor) { | ||||||
|  |                 // The destination surface is either a framebuffer, or a filtered texture. | ||||||
|  |                 OGLTexture tmp_tex; | ||||||
|  |                 tmp_tex.Create(); | ||||||
|  |                 // Create an intermediate surface to convert to before blitting to the | ||||||
|  |                 // destination. | ||||||
|  |                 Common::Rectangle<u32> tmp_rect{0, dest_rect.GetHeight() / resolution_scale_factor, | ||||||
|  |                                                 dest_rect.GetWidth() / resolution_scale_factor, 0}; | ||||||
|  |                 AllocateSurfaceTexture(tmp_tex.handle, | ||||||
|  |                                        GetFormatTuple(reinterpreter->first.dst_format), | ||||||
|  |                                        tmp_rect.right, tmp_rect.top); | ||||||
|  |                 reinterpreter->second->Reinterpret(reinterpret_surface->texture.handle, src_rect, | ||||||
|  |                                                    read_framebuffer.handle, tmp_tex.handle, | ||||||
|  |                                                    tmp_rect, draw_framebuffer.handle); | ||||||
|  |                 SurfaceParams::SurfaceType type = | ||||||
|  |                     SurfaceParams::GetFormatType(reinterpreter->first.dst_format); | ||||||
|  |  | ||||||
|  |                 if (!texture_filterer->Filter(tmp_tex.handle, tmp_rect, surface->texture.handle, | ||||||
|  |                                               dest_rect, type, read_framebuffer.handle, | ||||||
|  |                                               draw_framebuffer.handle)) { | ||||||
|  |                     BlitTextures(tmp_tex.handle, tmp_rect, surface->texture.handle, dest_rect, type, | ||||||
|  |                                  read_framebuffer.handle, draw_framebuffer.handle); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 reinterpreter->second->Reinterpret(reinterpret_surface->texture.handle, src_rect, | ||||||
|  |                                                    read_framebuffer.handle, surface->texture.handle, | ||||||
|  |                                                    dest_rect, draw_framebuffer.handle); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) { | void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) { | ||||||
|     if (size == 0) |     if (size == 0) | ||||||
|         return; |         return; | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ namespace OpenGL { | |||||||
|  |  | ||||||
| class RasterizerCacheOpenGL; | class RasterizerCacheOpenGL; | ||||||
| class TextureFilterer; | class TextureFilterer; | ||||||
|  | class FormatReinterpreterOpenGL; | ||||||
|  |  | ||||||
| struct TextureCubeConfig { | struct TextureCubeConfig { | ||||||
|     PAddr px; |     PAddr px; | ||||||
| @@ -240,9 +241,6 @@ public: | |||||||
|     bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect, |     bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect, | ||||||
|                       const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect); |                       const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect); | ||||||
|  |  | ||||||
|     void ConvertD24S8toABGR(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex, |  | ||||||
|                             const Common::Rectangle<u32>& dst_rect); |  | ||||||
|  |  | ||||||
|     /// Copy one surface's region to another |     /// Copy one surface's region to another | ||||||
|     void CopySurface(const Surface& src_surface, const Surface& dst_surface, |     void CopySurface(const Surface& src_surface, const Surface& dst_surface, | ||||||
|                      SurfaceInterval copy_interval); |                      SurfaceInterval copy_interval); | ||||||
| @@ -288,6 +286,18 @@ private: | |||||||
|     /// Update surface's texture for given region when necessary |     /// Update surface's texture for given region when necessary | ||||||
|     void ValidateSurface(const Surface& surface, PAddr addr, u32 size); |     void ValidateSurface(const Surface& surface, PAddr addr, u32 size); | ||||||
|  |  | ||||||
|  |     // Returns false if there is a surface in the cache at the interval with the same bit-width, | ||||||
|  |     bool NoUnimplementedReinterpretations(const OpenGL::Surface& surface, | ||||||
|  |                                           OpenGL::SurfaceParams& params, | ||||||
|  |                                           const OpenGL::SurfaceInterval& interval); | ||||||
|  |  | ||||||
|  |     // Return true if a surface with an invalid pixel format exists at the interval | ||||||
|  |     bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval); | ||||||
|  |  | ||||||
|  |     // Attempt to find a reinterpretable surface in the cache and use it to copy for validation | ||||||
|  |     bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params, | ||||||
|  |                                     const SurfaceInterval& interval); | ||||||
|  |  | ||||||
|     /// Create a new surface |     /// Create a new surface | ||||||
|     Surface CreateSurface(const SurfaceParams& params); |     Surface CreateSurface(const SurfaceParams& params); | ||||||
|  |  | ||||||
| @@ -308,18 +318,13 @@ private: | |||||||
|     OGLFramebuffer read_framebuffer; |     OGLFramebuffer read_framebuffer; | ||||||
|     OGLFramebuffer draw_framebuffer; |     OGLFramebuffer draw_framebuffer; | ||||||
|  |  | ||||||
|     OGLVertexArray attributeless_vao; |  | ||||||
|     OGLBuffer d24s8_abgr_buffer; |  | ||||||
|     GLsizeiptr d24s8_abgr_buffer_size; |  | ||||||
|     OGLProgram d24s8_abgr_shader; |  | ||||||
|     GLint d24s8_abgr_tbo_size_u_id; |  | ||||||
|     GLint d24s8_abgr_viewport_u_id; |  | ||||||
|     u16 resolution_scale_factor; |     u16 resolution_scale_factor; | ||||||
|  |  | ||||||
|     std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache; |     std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache; | ||||||
|  |  | ||||||
| public: | public: | ||||||
|     std::unique_ptr<TextureFilterer> texture_filterer; |     std::unique_ptr<TextureFilterer> texture_filterer; | ||||||
|  |     std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct FormatTuple { | struct FormatTuple { | ||||||
|   | |||||||
| @@ -91,6 +91,47 @@ public: | |||||||
|         return GetFormatBpp(pixel_format); |         return GetFormatBpp(pixel_format); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static std::string_view PixelFormatAsString(PixelFormat format) { | ||||||
|  |         switch (format) { | ||||||
|  |         case PixelFormat::RGBA8: | ||||||
|  |             return "RGBA8"; | ||||||
|  |         case PixelFormat::RGB8: | ||||||
|  |             return "RGB8"; | ||||||
|  |         case PixelFormat::RGB5A1: | ||||||
|  |             return "RGB5A1"; | ||||||
|  |         case PixelFormat::RGB565: | ||||||
|  |             return "RGB565"; | ||||||
|  |         case PixelFormat::RGBA4: | ||||||
|  |             return "RGBA4"; | ||||||
|  |         case PixelFormat::IA8: | ||||||
|  |             return "IA8"; | ||||||
|  |         case PixelFormat::RG8: | ||||||
|  |             return "RG8"; | ||||||
|  |         case PixelFormat::I8: | ||||||
|  |             return "I8"; | ||||||
|  |         case PixelFormat::A8: | ||||||
|  |             return "A8"; | ||||||
|  |         case PixelFormat::IA4: | ||||||
|  |             return "IA4"; | ||||||
|  |         case PixelFormat::I4: | ||||||
|  |             return "I4"; | ||||||
|  |         case PixelFormat::A4: | ||||||
|  |             return "A4"; | ||||||
|  |         case PixelFormat::ETC1: | ||||||
|  |             return "ETC1"; | ||||||
|  |         case PixelFormat::ETC1A4: | ||||||
|  |             return "ETC1A4"; | ||||||
|  |         case PixelFormat::D16: | ||||||
|  |             return "D16"; | ||||||
|  |         case PixelFormat::D24: | ||||||
|  |             return "D24"; | ||||||
|  |         case PixelFormat::D24S8: | ||||||
|  |             return "D24S8"; | ||||||
|  |         default: | ||||||
|  |             return "Not a real pixel format"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { |     static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) { | ||||||
|         return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; |         return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user