1 // Copyright 2020 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <chrono>
6 #include <vector>
7 
8 #include <fmt/chrono.h>
9 
10 #include "common/logging/log.h"
11 #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
12 #include "video_core/renderer_opengl/gl_state.h"
13 #include "video_core/renderer_opengl/gl_vars.h"
14 #include "video_core/renderer_opengl/texture_downloader_es.h"
15 
16 #include "shaders/depth_to_color.frag"
17 #include "shaders/depth_to_color.vert"
18 #include "shaders/ds_to_color.frag"
19 
20 namespace OpenGL {
21 
22 /**
23  * Self tests for the texture downloader
24  */
Test()25 void TextureDownloaderES::Test() {
26     auto cur_state = OpenGLState::GetCurState();
27     OpenGLState state;
28 
29     {
30         GLint range[2];
31         GLint precision;
32 #define PRECISION_TEST(type)                                                                       \
33     glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, type, range, &precision);                       \
34     LOG_INFO(Render_OpenGL, #type " range: [{}, {}], precision: {}", range[0], range[1], precision);
35         PRECISION_TEST(GL_LOW_INT);
36         PRECISION_TEST(GL_MEDIUM_INT);
37         PRECISION_TEST(GL_HIGH_INT);
38         PRECISION_TEST(GL_LOW_FLOAT);
39         PRECISION_TEST(GL_MEDIUM_FLOAT);
40         PRECISION_TEST(GL_HIGH_FLOAT);
41 #undef PRECISION_TEST
42     }
43     glActiveTexture(GL_TEXTURE0);
44 
45     const auto test = [this, &state](FormatTuple tuple, auto original_data, std::size_t tex_size,
46                                      auto data_generator) {
47         OGLTexture texture;
48         texture.Create();
49         state.texture_units[0].texture_2d = texture.handle;
50         state.Apply();
51 
52         original_data.resize(tex_size * tex_size);
53         for (std::size_t idx = 0; idx < original_data.size(); ++idx)
54             original_data[idx] = data_generator(idx);
55         glTexStorage2D(GL_TEXTURE_2D, 1, tuple.internal_format, tex_size, tex_size);
56         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_size, tex_size, tuple.format, tuple.type,
57                         original_data.data());
58 
59         decltype(original_data) new_data(original_data.size());
60         glFinish();
61         auto start = std::chrono::high_resolution_clock::now();
62         GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, tex_size, tex_size,
63                     new_data.data());
64         glFinish();
65         auto time = std::chrono::high_resolution_clock::now() - start;
66         LOG_INFO(Render_OpenGL, "test took {}", std::chrono::duration<double, std::milli>(time));
67 
68         int diff = 0;
69         for (std::size_t idx = 0; idx < original_data.size(); ++idx)
70             if (new_data[idx] - original_data[idx] != diff) {
71                 diff = new_data[idx] - original_data[idx];
72                 // every time the error between the real and expected value changes, log it
73                 // some error is expected in D24 due to floating point precision
74                 LOG_WARNING(Render_OpenGL, "difference changed at {:#X}: {:#X} -> {:#X}", idx,
75                             original_data[idx], new_data[idx]);
76             }
77     };
78     LOG_INFO(Render_OpenGL, "GL_DEPTH24_STENCIL8 download test starting");
79     test(depth_format_tuples[3], std::vector<u32>{}, 4096,
80          [](std::size_t idx) { return static_cast<u32>((idx << 8) | (idx & 0xFF)); });
81     LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT24 download test starting");
82     test(depth_format_tuples[2], std::vector<u32>{}, 4096,
83          [](std::size_t idx) { return static_cast<u32>(idx << 8); });
84     LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT16 download test starting");
85     test(depth_format_tuples[0], std::vector<u16>{}, 256,
86          [](std::size_t idx) { return static_cast<u16>(idx); });
87 
88     cur_state.Apply();
89 }
90 
TextureDownloaderES(bool enable_depth_stencil)91 TextureDownloaderES::TextureDownloaderES(bool enable_depth_stencil) {
92     vao.Create();
93     read_fbo_generic.Create();
94 
95     depth32_fbo.Create();
96     r32ui_renderbuffer.Create();
97     depth16_fbo.Create();
98     r16_renderbuffer.Create();
99 
100     const auto init_program = [](ConversionShader& converter, std::string_view frag) {
101         converter.program.Create(depth_to_color_vert.data(), frag.data());
102         converter.lod_location = glGetUniformLocation(converter.program.handle, "lod");
103     };
104 
105     // xperia64: The depth stencil shader currently uses a GLES extension that is not supported
106     // across all devices Reportedly broken on Tegra devices and the Nexus 6P, so enabling it can be
107     // toggled
108     if (enable_depth_stencil) {
109         init_program(d24s8_r32ui_conversion_shader, ds_to_color_frag);
110     }
111 
112     init_program(d24_r32ui_conversion_shader, depth_to_color_frag);
113     init_program(d16_r16_conversion_shader, R"(
114 out highp float color;
115 
116 uniform highp sampler2D depth;
117 uniform int lod;
118 
119 void main(){
120     color = texelFetch(depth, ivec2(gl_FragCoord.xy), lod).x;
121 }
122 )");
123 
124     sampler.Create();
125     glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
126     glSamplerParameteri(sampler.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
127 
128     auto cur_state = OpenGLState::GetCurState();
129     auto state = cur_state;
130 
131     state.draw.shader_program = d24s8_r32ui_conversion_shader.program.handle;
132     state.draw.draw_framebuffer = depth32_fbo.handle;
133     state.renderbuffer = r32ui_renderbuffer.handle;
134     state.Apply();
135     glRenderbufferStorage(GL_RENDERBUFFER, GL_R32UI, max_size, max_size);
136     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
137                               r32ui_renderbuffer.handle);
138     glUniform1i(glGetUniformLocation(d24s8_r32ui_conversion_shader.program.handle, "depth"), 1);
139 
140     state.draw.draw_framebuffer = depth16_fbo.handle;
141     state.renderbuffer = r16_renderbuffer.handle;
142     state.Apply();
143     glRenderbufferStorage(GL_RENDERBUFFER, GL_R16, max_size, max_size);
144     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
145                               r16_renderbuffer.handle);
146 
147     cur_state.Apply();
148 }
149 
150 /**
151  * OpenGL ES does not support glReadBuffer for depth/stencil formats
152  * This gets around it by converting to a Red surface before downloading
153  */
ConvertDepthToColor(GLuint level,GLenum & format,GLenum & type,GLint height,GLint width)154 GLuint TextureDownloaderES::ConvertDepthToColor(GLuint level, GLenum& format, GLenum& type,
155                                                 GLint height, GLint width) {
156     ASSERT(width <= max_size && height <= max_size);
157     const OpenGLState cur_state = OpenGLState::GetCurState();
158     OpenGLState state;
159     state.texture_units[0] = {cur_state.texture_units[0].texture_2d, sampler.handle};
160     state.draw.vertex_array = vao.handle;
161 
162     OGLTexture texture_view;
163     const ConversionShader* converter;
164     switch (type) {
165     case GL_UNSIGNED_SHORT:
166         state.draw.draw_framebuffer = depth16_fbo.handle;
167         converter = &d16_r16_conversion_shader;
168         format = GL_RED;
169         break;
170     case GL_UNSIGNED_INT:
171         state.draw.draw_framebuffer = depth32_fbo.handle;
172         converter = &d24_r32ui_conversion_shader;
173         format = GL_RED_INTEGER;
174         break;
175     case GL_UNSIGNED_INT_24_8:
176         state.draw.draw_framebuffer = depth32_fbo.handle;
177         converter = &d24s8_r32ui_conversion_shader;
178         format = GL_RED_INTEGER;
179         type = GL_UNSIGNED_INT;
180         break;
181     default:
182         UNREACHABLE_MSG("Destination type not recognized");
183     }
184     state.draw.shader_program = converter->program.handle;
185     state.viewport = {0, 0, width, height};
186     state.Apply();
187     if (converter->program.handle == d24s8_r32ui_conversion_shader.program.handle) {
188         // TODO BreadFish64: the ARM framebuffer reading extension is probably not the most optimal
189         // way to do this, search for another solution
190         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
191                                state.texture_units[0].texture_2d, level);
192     }
193 
194     glUniform1i(converter->lod_location, level);
195     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
196     if (texture_view.handle) {
197         glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
198     }
199     return state.draw.draw_framebuffer;
200 }
201 
202 /**
203  * OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the
204  * texture to a framebuffer.
205  * Originally from https://github.com/apitrace/apitrace/blob/master/retrace/glstate_images.cpp
206  * Depth texture download assumes that the texture's format tuple matches what is found
207  * OpenGL::depth_format_tuples
208  */
GetTexImage(GLenum target,GLuint level,GLenum format,GLenum type,GLint height,GLint width,void * pixels)209 void TextureDownloaderES::GetTexImage(GLenum target, GLuint level, GLenum format, GLenum type,
210                                       GLint height, GLint width, void* pixels) {
211     OpenGLState state = OpenGLState::GetCurState();
212     GLuint texture;
213     const GLuint old_read_buffer = state.draw.read_framebuffer;
214     switch (target) {
215     case GL_TEXTURE_2D:
216         texture = state.texture_units[0].texture_2d;
217         break;
218     case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
219     case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
220     case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
221     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
222     case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
223     case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
224         texture = state.texture_cube_unit.texture_cube;
225         break;
226     default:
227         UNIMPLEMENTED_MSG("Unexpected target {:x}", target);
228     }
229 
230     switch (format) {
231     case GL_DEPTH_COMPONENT:
232     case GL_DEPTH_STENCIL:
233         // unfortunately, the accurate way is too slow for release
234         return;
235         state.draw.read_framebuffer = ConvertDepthToColor(level, format, type, height, width);
236         state.Apply();
237         break;
238     default:
239         state.draw.read_framebuffer = read_fbo_generic.handle;
240         state.Apply();
241         glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
242                                level);
243     }
244     GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
245     if (status != GL_FRAMEBUFFER_COMPLETE) {
246         LOG_DEBUG(Render_OpenGL, "Framebuffer is incomplete, status: {:X}", status);
247     }
248     glReadPixels(0, 0, width, height, format, type, pixels);
249 
250     state.draw.read_framebuffer = old_read_buffer;
251     state.Apply();
252 }
253 
254 } // namespace OpenGL
255