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