mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	video_core: implement GLES depth/stencil downloads
video_core: disable depth/stencil texture download on OpenGL ES Disable deptch stencil shader in texture_downloader_es for now enable_depth_stencil DepthStencil remove GL_DEBUG_OUTPUT_SYNCHRONOUS
This commit is contained in:
		
							parent
							
								
									91f52c2fdb
								
							
						
					
					
						commit
						54b8af1444
					
				
					 9 changed files with 359 additions and 103 deletions
				
			
		|  | @ -54,6 +54,8 @@ add_library(video_core STATIC | ||||||
|     renderer_opengl/post_processing_opengl.h |     renderer_opengl/post_processing_opengl.h | ||||||
|     renderer_opengl/renderer_opengl.cpp |     renderer_opengl/renderer_opengl.cpp | ||||||
|     renderer_opengl/renderer_opengl.h |     renderer_opengl/renderer_opengl.h | ||||||
|  |     renderer_opengl/texture_downloader_es.cpp | ||||||
|  |     renderer_opengl/texture_downloader_es.h | ||||||
|     renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp |     renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp | ||||||
|     renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h |     renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h | ||||||
|     renderer_opengl/texture_filters/bicubic/bicubic.cpp |     renderer_opengl/texture_filters/bicubic/bicubic.cpp | ||||||
|  | @ -99,6 +101,9 @@ add_library(video_core STATIC | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| set(SHADER_FILES | set(SHADER_FILES | ||||||
|  |     renderer_opengl/depth_to_color.frag | ||||||
|  |     renderer_opengl/depth_to_color.vert | ||||||
|  |     renderer_opengl/ds_to_color.frag | ||||||
|     renderer_opengl/texture_filters/anime4k/refine.frag |     renderer_opengl/texture_filters/anime4k/refine.frag | ||||||
|     renderer_opengl/texture_filters/anime4k/x_gradient.frag |     renderer_opengl/texture_filters/anime4k/x_gradient.frag | ||||||
|     renderer_opengl/texture_filters/anime4k/y_gradient.frag |     renderer_opengl/texture_filters/anime4k/y_gradient.frag | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								src/video_core/renderer_opengl/depth_to_color.frag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/video_core/renderer_opengl/depth_to_color.frag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | //? #version 320 es | ||||||
|  | 
 | ||||||
|  | out highp uint color; | ||||||
|  | 
 | ||||||
|  | uniform highp sampler2D depth; | ||||||
|  | uniform int lod; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  |     color = uint(texelFetch(depth, ivec2(gl_FragCoord.xy), lod).x * (exp2(32.0) - 1.0)); | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/video_core/renderer_opengl/depth_to_color.vert
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/video_core/renderer_opengl/depth_to_color.vert
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | //? #version 320 es | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								src/video_core/renderer_opengl/ds_to_color.frag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/video_core/renderer_opengl/ds_to_color.frag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | //? #version 320 es | ||||||
|  | #extension GL_ARM_shader_framebuffer_fetch_depth_stencil : enable | ||||||
|  | 
 | ||||||
|  | out highp uint color; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  |     color = uint(gl_LastFragDepthARM * (exp2(24.0) - 1.0)) << 8; | ||||||
|  |     color |= uint(gl_LastFragStencilARM); | ||||||
|  | } | ||||||
|  | @ -36,6 +36,7 @@ | ||||||
| #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" | ||||||
|  | #include "video_core/renderer_opengl/texture_downloader_es.h" | ||||||
| #include "video_core/renderer_opengl/texture_filters/texture_filterer.h" | #include "video_core/renderer_opengl/texture_filters/texture_filterer.h" | ||||||
| #include "video_core/utils.h" | #include "video_core/utils.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
|  | @ -64,13 +65,6 @@ static constexpr std::array<FormatTuple, 5> fb_format_tuples_oes = {{ | ||||||
|     {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4},   // RGBA4
 |     {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4},   // RGBA4
 | ||||||
| }}; | }}; | ||||||
| 
 | 
 | ||||||
| static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{ |  | ||||||
|     {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
 |  | ||||||
|     {}, |  | ||||||
|     {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT},   // D24
 |  | ||||||
|     {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
 |  | ||||||
| }}; |  | ||||||
| 
 |  | ||||||
| const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { | const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { | ||||||
|     const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); |     const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); | ||||||
|     if (type == SurfaceType::Color) { |     if (type == SurfaceType::Color) { | ||||||
|  | @ -87,79 +81,6 @@ const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { | ||||||
|     return tex_tuple; |     return tex_tuple; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 |  | ||||||
|  * OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the |  | ||||||
|  * texture to a framebuffer. |  | ||||||
|  * Originally from https://github.com/apitrace/apitrace/blob/master/retrace/glstate_images.cpp
 |  | ||||||
|  */ |  | ||||||
| static void GetTexImageOES(GLenum target, GLint level, GLenum format, GLenum type, GLint height, |  | ||||||
|                            GLint width, GLint depth, GLubyte* pixels, std::size_t size) { |  | ||||||
|     memset(pixels, 0x80, size); |  | ||||||
| 
 |  | ||||||
|     OpenGLState cur_state = OpenGLState::GetCurState(); |  | ||||||
|     OpenGLState state; |  | ||||||
| 
 |  | ||||||
|     GLenum texture_binding = GL_NONE; |  | ||||||
|     switch (target) { |  | ||||||
|     case GL_TEXTURE_2D: |  | ||||||
|         texture_binding = cur_state.texture_units[0].texture_2d; |  | ||||||
|         break; |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_X: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: |  | ||||||
|         texture_binding = cur_state.texture_cube_unit.texture_cube; |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         LOG_CRITICAL(Render_OpenGL, "Unexpected target {:x}", target); |  | ||||||
|         UNIMPLEMENTED(); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     GLint texture = 0; |  | ||||||
|     glGetIntegerv(texture_binding, &texture); |  | ||||||
|     if (!texture) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     OGLFramebuffer fbo; |  | ||||||
|     fbo.Create(); |  | ||||||
|     state.draw.read_framebuffer = fbo.handle; |  | ||||||
|     state.Apply(); |  | ||||||
| 
 |  | ||||||
|     switch (target) { |  | ||||||
|     case GL_TEXTURE_2D: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_X: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: |  | ||||||
|     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: { |  | ||||||
|         glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, |  | ||||||
|                                level); |  | ||||||
|         GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); |  | ||||||
|         if (status != GL_FRAMEBUFFER_COMPLETE) { |  | ||||||
|             LOG_DEBUG(Render_OpenGL, "Framebuffer is incomplete, status: {:X}", status); |  | ||||||
|         } |  | ||||||
|         glReadPixels(0, 0, width, height, format, type, pixels); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case GL_TEXTURE_3D_OES: |  | ||||||
|         for (int i = 0; i < depth; i++) { |  | ||||||
|             glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, |  | ||||||
|                                    texture, level, i); |  | ||||||
|             glReadPixels(0, 0, width, height, format, type, pixels + 4 * i * width * height); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     cur_state.Apply(); |  | ||||||
| 
 |  | ||||||
|     fbo.Release(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template <typename Map, typename Interval> | template <typename Map, typename Interval> | ||||||
| static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { | static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { | ||||||
|     return boost::make_iterator_range(map.equal_range(interval)); |     return boost::make_iterator_range(map.equal_range(interval)); | ||||||
|  | @ -775,23 +696,28 @@ void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) { | ||||||
|         LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path); |         LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path); | ||||||
|         std::vector<u8> decoded_texture; |         std::vector<u8> decoded_texture; | ||||||
|         decoded_texture.resize(width * height * 4); |         decoded_texture.resize(width * height * 4); | ||||||
|         glBindTexture(GL_TEXTURE_2D, target_tex); |         OpenGLState state = OpenGLState::GetCurState(); | ||||||
|  |         GLuint old_texture = state.texture_units[0].texture_2d; | ||||||
|  |         state.Apply(); | ||||||
|         /*
 |         /*
 | ||||||
|            GetTexImageOES is used even if not using OpenGL ES to work around a small issue that |            GetTexImageOES is used even if not using OpenGL ES to work around a small issue that | ||||||
|            happens if using custom textures with texture dumping at the same. |            happens if using custom textures with texture dumping at the same. | ||||||
|            Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a |            Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a | ||||||
|            higher quality 256x256 texture. If the 256x256 texture is displayed first and the 32x32 |            higher quality 256x256 texture. If the 256x256 texture is displayed first and the | ||||||
|            texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture will |            32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture | ||||||
|            appear in the corner of the 256x256 texture. |            will appear in the corner of the 256x256 texture. If texture dumping is enabled and | ||||||
|            If texture dumping is enabled and the 32x32 is undumped, Citra will attempt to dump it. |            the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL | ||||||
|            Since the underlying OpenGL texture is still 256x256, Citra crashes because it thinks the |            texture is still 256x256, Citra crashes because it thinks the texture is only 32x32. | ||||||
|            texture is only 32x32. |  | ||||||
|            GetTexImageOES conveniently only dumps the specified region, and works on both |            GetTexImageOES conveniently only dumps the specified region, and works on both | ||||||
|            desktop and ES. |            desktop and ES. | ||||||
|         */ |         */ | ||||||
|         GetTexImageOES(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, height, width, 0, |         // if the backend isn't OpenGL ES, this won't be initialized yet
 | ||||||
|                        &decoded_texture[0], decoded_texture.size()); |         if (!owner.texture_downloader_es) | ||||||
|         glBindTexture(GL_TEXTURE_2D, 0); |             owner.texture_downloader_es = std::make_unique<TextureDownloaderES>(false); | ||||||
|  |         owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, | ||||||
|  |                                                  height, width, &decoded_texture[0]); | ||||||
|  |         state.texture_units[0].texture_2d = old_texture; | ||||||
|  |         state.Apply(); | ||||||
|         Common::FlipRGBA8Texture(decoded_texture, width, height); |         Common::FlipRGBA8Texture(decoded_texture, width, height); | ||||||
|         if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height)) |         if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height)) | ||||||
|             LOG_ERROR(Render_OpenGL, "Failed to save decoded texture"); |             LOG_ERROR(Render_OpenGL, "Failed to save decoded texture"); | ||||||
|  | @ -905,14 +831,6 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (GLES) { |  | ||||||
|         if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { |  | ||||||
|             // TODO(bunnei): This is unsupported on GLES right now, fixme
 |  | ||||||
|             LOG_WARNING(Render_OpenGL, "Unsupported depth/stencil surface download"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MICROPROFILE_SCOPE(OpenGL_TextureDL); |     MICROPROFILE_SCOPE(OpenGL_TextureDL); | ||||||
| 
 | 
 | ||||||
|     if (gl_buffer.empty()) { |     if (gl_buffer.empty()) { | ||||||
|  | @ -950,9 +868,9 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint | ||||||
| 
 | 
 | ||||||
|         glActiveTexture(GL_TEXTURE0); |         glActiveTexture(GL_TEXTURE0); | ||||||
|         if (GLES) { |         if (GLES) { | ||||||
|             GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, rect.GetHeight(), |             owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, | ||||||
|                            rect.GetWidth(), 0, &gl_buffer[buffer_offset], |                                                      rect.GetHeight(), rect.GetWidth(), | ||||||
|                            gl_buffer.size() - buffer_offset); |                                                      &gl_buffer[buffer_offset]); | ||||||
|         } else { |         } else { | ||||||
|             glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); |             glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); | ||||||
|         } |         } | ||||||
|  | @ -1106,6 +1024,8 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() { | ||||||
|     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>(); |     format_reinterpreter = std::make_unique<FormatReinterpreterOpenGL>(); | ||||||
|  |     if (GLES) | ||||||
|  |         texture_downloader_es = std::make_unique<TextureDownloaderES>(false); | ||||||
| 
 | 
 | ||||||
|     read_framebuffer.Create(); |     read_framebuffer.Create(); | ||||||
|     draw_framebuffer.Create(); |     draw_framebuffer.Create(); | ||||||
|  |  | ||||||
|  | @ -171,6 +171,8 @@ private: | ||||||
|     bool valid = false; |     bool valid = false; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | class RasterizerCacheOpenGL; | ||||||
|  | 
 | ||||||
| struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> { | struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> { | ||||||
|     CachedSurface(RasterizerCacheOpenGL& owner) : owner{owner} {} |     CachedSurface(RasterizerCacheOpenGL& owner) : owner{owner} {} | ||||||
|     ~CachedSurface(); |     ~CachedSurface(); | ||||||
|  | @ -267,6 +269,15 @@ struct CachedTextureCube { | ||||||
|     std::shared_ptr<SurfaceWatcher> nz; |     std::shared_ptr<SurfaceWatcher> nz; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{ | ||||||
|  |     {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
 | ||||||
|  |     {}, | ||||||
|  |     {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT},   // D24
 | ||||||
|  |     {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
 | ||||||
|  | }}; | ||||||
|  | 
 | ||||||
|  | class TextureDownloaderES; | ||||||
|  | 
 | ||||||
| class RasterizerCacheOpenGL : NonCopyable { | class RasterizerCacheOpenGL : NonCopyable { | ||||||
| public: | public: | ||||||
|     RasterizerCacheOpenGL(); |     RasterizerCacheOpenGL(); | ||||||
|  | @ -373,6 +384,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<TextureFilterer> texture_filterer; |     std::unique_ptr<TextureFilterer> texture_filterer; | ||||||
|     std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter; |     std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter; | ||||||
|  |     std::unique_ptr<TextureDownloaderES> texture_downloader_es; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace OpenGL
 | } // namespace OpenGL
 | ||||||
|  |  | ||||||
|  | @ -12,10 +12,12 @@ namespace OpenGL { | ||||||
| // High precision may or may not supported in GLES3. If it isn't, use medium precision instead.
 | // High precision may or may not supported in GLES3. If it isn't, use medium precision instead.
 | ||||||
| static constexpr char fragment_shader_precision_OES[] = R"( | static constexpr char fragment_shader_precision_OES[] = R"( | ||||||
| #ifdef GL_FRAGMENT_PRECISION_HIGH | #ifdef GL_FRAGMENT_PRECISION_HIGH | ||||||
|     precision highp float; | precision highp int; | ||||||
|  | precision highp float; | ||||||
| precision highp samplerBuffer; | precision highp samplerBuffer; | ||||||
| #else | #else | ||||||
|     precision mediump float; | precision mediump int; | ||||||
|  | precision mediump float; | ||||||
| precision mediump samplerBuffer; | precision mediump samplerBuffer; | ||||||
| #endif // GL_FRAGMENT_PRECISION_HIGH
 | #endif // GL_FRAGMENT_PRECISION_HIGH
 | ||||||
| )"; | )"; | ||||||
|  |  | ||||||
							
								
								
									
										254
									
								
								src/video_core/renderer_opengl/texture_downloader_es.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								src/video_core/renderer_opengl/texture_downloader_es.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,254 @@ | ||||||
|  | // Copyright 2020 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <chrono> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <fmt/chrono.h> | ||||||
|  | 
 | ||||||
|  | #include "common/logging/log.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_downloader_es.h" | ||||||
|  | 
 | ||||||
|  | #include "shaders/depth_to_color.frag" | ||||||
|  | #include "shaders/depth_to_color.vert" | ||||||
|  | #include "shaders/ds_to_color.frag" | ||||||
|  | 
 | ||||||
|  | namespace OpenGL { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Self tests for the texture downloader | ||||||
|  |  */ | ||||||
|  | void TextureDownloaderES::Test() { | ||||||
|  |     auto cur_state = OpenGLState::GetCurState(); | ||||||
|  |     OpenGLState state; | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         GLint range[2]; | ||||||
|  |         GLint precision; | ||||||
|  | #define PRECISION_TEST(type)                                                                       \ | ||||||
|  |     glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, type, range, &precision);                       \ | ||||||
|  |     LOG_INFO(Render_OpenGL, #type " range: [{}, {}], precision: {}", range[0], range[1], precision); | ||||||
|  |         PRECISION_TEST(GL_LOW_INT); | ||||||
|  |         PRECISION_TEST(GL_MEDIUM_INT); | ||||||
|  |         PRECISION_TEST(GL_HIGH_INT); | ||||||
|  |         PRECISION_TEST(GL_LOW_FLOAT); | ||||||
|  |         PRECISION_TEST(GL_MEDIUM_FLOAT); | ||||||
|  |         PRECISION_TEST(GL_HIGH_FLOAT); | ||||||
|  | #undef PRECISION_TEST | ||||||
|  |     } | ||||||
|  |     glActiveTexture(GL_TEXTURE0); | ||||||
|  | 
 | ||||||
|  |     const auto test = [this, &state](FormatTuple tuple, auto original_data, std::size_t tex_size, | ||||||
|  |                                      auto data_generator) { | ||||||
|  |         OGLTexture texture; | ||||||
|  |         texture.Create(); | ||||||
|  |         state.texture_units[0].texture_2d = texture.handle; | ||||||
|  |         state.Apply(); | ||||||
|  | 
 | ||||||
|  |         original_data.resize(tex_size * tex_size); | ||||||
|  |         for (std::size_t idx = 0; idx < original_data.size(); ++idx) | ||||||
|  |             original_data[idx] = data_generator(idx); | ||||||
|  |         glTexStorage2D(GL_TEXTURE_2D, 1, tuple.internal_format, tex_size, tex_size); | ||||||
|  |         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_size, tex_size, tuple.format, tuple.type, | ||||||
|  |                         original_data.data()); | ||||||
|  | 
 | ||||||
|  |         decltype(original_data) new_data(original_data.size()); | ||||||
|  |         glFinish(); | ||||||
|  |         auto start = std::chrono::high_resolution_clock::now(); | ||||||
|  |         GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, tex_size, tex_size, | ||||||
|  |                     new_data.data()); | ||||||
|  |         glFinish(); | ||||||
|  |         auto time = std::chrono::high_resolution_clock::now() - start; | ||||||
|  |         LOG_INFO(Render_OpenGL, "test took {}", std::chrono::duration<double, std::milli>(time)); | ||||||
|  | 
 | ||||||
|  |         int diff = 0; | ||||||
|  |         for (std::size_t idx = 0; idx < original_data.size(); ++idx) | ||||||
|  |             if (new_data[idx] - original_data[idx] != diff) { | ||||||
|  |                 diff = new_data[idx] - original_data[idx]; | ||||||
|  |                 // every time the error between the real and expected value changes, log it
 | ||||||
|  |                 // some error is expected in D24 due to floating point precision
 | ||||||
|  |                 LOG_WARNING(Render_OpenGL, "difference changed at {:#X}: {:#X} -> {:#X}", idx, | ||||||
|  |                             original_data[idx], new_data[idx]); | ||||||
|  |             } | ||||||
|  |     }; | ||||||
|  |     LOG_INFO(Render_OpenGL, "GL_DEPTH24_STENCIL8 download test starting"); | ||||||
|  |     test(depth_format_tuples[3], std::vector<u32>{}, 4096, | ||||||
|  |          [](std::size_t idx) { return static_cast<u32>((idx << 8) | (idx & 0xFF)); }); | ||||||
|  |     LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT24 download test starting"); | ||||||
|  |     test(depth_format_tuples[2], std::vector<u32>{}, 4096, | ||||||
|  |          [](std::size_t idx) { return static_cast<u32>(idx << 8); }); | ||||||
|  |     LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT16 download test starting"); | ||||||
|  |     test(depth_format_tuples[0], std::vector<u16>{}, 256, | ||||||
|  |          [](std::size_t idx) { return static_cast<u16>(idx); }); | ||||||
|  | 
 | ||||||
|  |     cur_state.Apply(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TextureDownloaderES::TextureDownloaderES(bool enable_depth_stencil) { | ||||||
|  |     vao.Create(); | ||||||
|  |     read_fbo_generic.Create(); | ||||||
|  | 
 | ||||||
|  |     depth32_fbo.Create(); | ||||||
|  |     r32ui_renderbuffer.Create(); | ||||||
|  |     depth16_fbo.Create(); | ||||||
|  |     r16_renderbuffer.Create(); | ||||||
|  | 
 | ||||||
|  |     const auto init_program = [](ConversionShader& converter, std::string_view frag) { | ||||||
|  |         converter.program.Create(depth_to_color_vert.data(), frag.data()); | ||||||
|  |         converter.lod_location = glGetUniformLocation(converter.program.handle, "lod"); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // xperia64: The depth stencil shader currently uses a GLES extension that is not supported
 | ||||||
|  |     // across all devices Reportedly broken on Tegra devices and the Nexus 6P, so enabling it can be
 | ||||||
|  |     // toggled
 | ||||||
|  |     if (enable_depth_stencil) { | ||||||
|  |         init_program(d24s8_r32ui_conversion_shader, ds_to_color_frag); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     init_program(d24_r32ui_conversion_shader, depth_to_color_frag); | ||||||
|  |     init_program(d16_r16_conversion_shader, R"( | ||||||
|  | out highp float color; | ||||||
|  | 
 | ||||||
|  | uniform highp sampler2D depth; | ||||||
|  | uniform int lod; | ||||||
|  | 
 | ||||||
|  | void main(){ | ||||||
|  |     color = texelFetch(depth, ivec2(gl_FragCoord.xy), lod).x; | ||||||
|  | } | ||||||
|  | )"); | ||||||
|  | 
 | ||||||
|  |     sampler.Create(); | ||||||
|  |     glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||||
|  |     glSamplerParameteri(sampler.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||||
|  | 
 | ||||||
|  |     auto cur_state = OpenGLState::GetCurState(); | ||||||
|  |     auto state = cur_state; | ||||||
|  | 
 | ||||||
|  |     state.draw.shader_program = d24s8_r32ui_conversion_shader.program.handle; | ||||||
|  |     state.draw.draw_framebuffer = depth32_fbo.handle; | ||||||
|  |     state.renderbuffer = r32ui_renderbuffer.handle; | ||||||
|  |     state.Apply(); | ||||||
|  |     glRenderbufferStorage(GL_RENDERBUFFER, GL_R32UI, max_size, max_size); | ||||||
|  |     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||||
|  |                               r32ui_renderbuffer.handle); | ||||||
|  |     glUniform1i(glGetUniformLocation(d24s8_r32ui_conversion_shader.program.handle, "depth"), 1); | ||||||
|  | 
 | ||||||
|  |     state.draw.draw_framebuffer = depth16_fbo.handle; | ||||||
|  |     state.renderbuffer = r16_renderbuffer.handle; | ||||||
|  |     state.Apply(); | ||||||
|  |     glRenderbufferStorage(GL_RENDERBUFFER, GL_R16, max_size, max_size); | ||||||
|  |     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, | ||||||
|  |                               r16_renderbuffer.handle); | ||||||
|  | 
 | ||||||
|  |     cur_state.Apply(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * OpenGL ES does not support glReadBuffer for depth/stencil formats | ||||||
|  |  * This gets around it by converting to a Red surface before downloading | ||||||
|  |  */ | ||||||
|  | GLuint TextureDownloaderES::ConvertDepthToColor(GLuint level, GLenum& format, GLenum& type, | ||||||
|  |                                                 GLint height, GLint width) { | ||||||
|  |     ASSERT(width <= max_size && height <= max_size); | ||||||
|  |     const OpenGLState cur_state = OpenGLState::GetCurState(); | ||||||
|  |     OpenGLState state; | ||||||
|  |     state.texture_units[0] = {cur_state.texture_units[0].texture_2d, sampler.handle}; | ||||||
|  |     state.draw.vertex_array = vao.handle; | ||||||
|  | 
 | ||||||
|  |     OGLTexture texture_view; | ||||||
|  |     const ConversionShader* converter; | ||||||
|  |     switch (type) { | ||||||
|  |     case GL_UNSIGNED_SHORT: | ||||||
|  |         state.draw.draw_framebuffer = depth16_fbo.handle; | ||||||
|  |         converter = &d16_r16_conversion_shader; | ||||||
|  |         format = GL_RED; | ||||||
|  |         break; | ||||||
|  |     case GL_UNSIGNED_INT: | ||||||
|  |         state.draw.draw_framebuffer = depth32_fbo.handle; | ||||||
|  |         converter = &d24_r32ui_conversion_shader; | ||||||
|  |         format = GL_RED_INTEGER; | ||||||
|  |         break; | ||||||
|  |     case GL_UNSIGNED_INT_24_8: | ||||||
|  |         state.draw.draw_framebuffer = depth32_fbo.handle; | ||||||
|  |         converter = &d24s8_r32ui_conversion_shader; | ||||||
|  |         format = GL_RED_INTEGER; | ||||||
|  |         type = GL_UNSIGNED_INT; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         UNREACHABLE_MSG("Destination type not recognized"); | ||||||
|  |     } | ||||||
|  |     state.draw.shader_program = converter->program.handle; | ||||||
|  |     state.viewport = {0, 0, width, height}; | ||||||
|  |     state.Apply(); | ||||||
|  |     if (converter->program.handle == d24s8_r32ui_conversion_shader.program.handle) { | ||||||
|  |         // TODO BreadFish64: the ARM framebuffer reading extension is probably not the most optimal
 | ||||||
|  |         // way to do this, search for another solution
 | ||||||
|  |         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||||||
|  |                                state.texture_units[0].texture_2d, level); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     glUniform1i(converter->lod_location, level); | ||||||
|  |     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||||
|  |     if (texture_view.handle) { | ||||||
|  |         glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT); | ||||||
|  |     } | ||||||
|  |     return state.draw.draw_framebuffer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the | ||||||
|  |  * texture to a framebuffer. | ||||||
|  |  * Originally from https://github.com/apitrace/apitrace/blob/master/retrace/glstate_images.cpp
 | ||||||
|  |  * Depth texture download assumes that the texture's format tuple matches what is found | ||||||
|  |  * OpenGL::depth_format_tuples | ||||||
|  |  */ | ||||||
|  | void TextureDownloaderES::GetTexImage(GLenum target, GLuint level, GLenum format, GLenum type, | ||||||
|  |                                       GLint height, GLint width, void* pixels) { | ||||||
|  |     OpenGLState state = OpenGLState::GetCurState(); | ||||||
|  |     GLuint texture; | ||||||
|  |     const GLuint old_read_buffer = state.draw.read_framebuffer; | ||||||
|  |     switch (target) { | ||||||
|  |     case GL_TEXTURE_2D: | ||||||
|  |         texture = state.texture_units[0].texture_2d; | ||||||
|  |         break; | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_POSITIVE_X: | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: | ||||||
|  |     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: | ||||||
|  |         texture = state.texture_cube_unit.texture_cube; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         UNIMPLEMENTED_MSG("Unexpected target {:x}", target); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     switch (format) { | ||||||
|  |     case GL_DEPTH_COMPONENT: | ||||||
|  |     case GL_DEPTH_STENCIL: | ||||||
|  |         // unfortunately, the accurate way is too slow for release
 | ||||||
|  |         return; | ||||||
|  |         state.draw.read_framebuffer = ConvertDepthToColor(level, format, type, height, width); | ||||||
|  |         state.Apply(); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         state.draw.read_framebuffer = read_fbo_generic.handle; | ||||||
|  |         state.Apply(); | ||||||
|  |         glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, | ||||||
|  |                                level); | ||||||
|  |     } | ||||||
|  |     GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); | ||||||
|  |     if (status != GL_FRAMEBUFFER_COMPLETE) { | ||||||
|  |         LOG_DEBUG(Render_OpenGL, "Framebuffer is incomplete, status: {:X}", status); | ||||||
|  |     } | ||||||
|  |     glReadPixels(0, 0, width, height, format, type, pixels); | ||||||
|  | 
 | ||||||
|  |     state.draw.read_framebuffer = old_read_buffer; | ||||||
|  |     state.Apply(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace OpenGL
 | ||||||
							
								
								
									
										36
									
								
								src/video_core/renderer_opengl/texture_downloader_es.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/video_core/renderer_opengl/texture_downloader_es.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | // Copyright 2020 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
|  | 
 | ||||||
|  | namespace OpenGL { | ||||||
|  | class OpenGLState; | ||||||
|  | 
 | ||||||
|  | class TextureDownloaderES { | ||||||
|  |     static constexpr u16 max_size = 1024; | ||||||
|  | 
 | ||||||
|  |     OGLVertexArray vao; | ||||||
|  |     OGLFramebuffer read_fbo_generic; | ||||||
|  |     OGLFramebuffer depth32_fbo, depth16_fbo; | ||||||
|  |     OGLRenderbuffer r32ui_renderbuffer, r16_renderbuffer; | ||||||
|  |     struct ConversionShader { | ||||||
|  |         OGLProgram program; | ||||||
|  |         GLint lod_location{-1}; | ||||||
|  |     } d24_r32ui_conversion_shader, d16_r16_conversion_shader, d24s8_r32ui_conversion_shader; | ||||||
|  |     OGLSampler sampler; | ||||||
|  | 
 | ||||||
|  |     void Test(); | ||||||
|  |     GLuint ConvertDepthToColor(GLuint level, GLenum& format, GLenum& type, GLint height, | ||||||
|  |                                GLint width); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     TextureDownloaderES(bool enable_depth_stencil); | ||||||
|  | 
 | ||||||
|  |     void GetTexImage(GLenum target, GLuint level, GLenum format, const GLenum type, GLint height, | ||||||
|  |                      GLint width, void* pixels); | ||||||
|  | }; | ||||||
|  | } // namespace OpenGL
 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue