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
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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,9 +12,11 @@ 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 int;
|
||||||
precision highp float;
|
precision highp float;
|
||||||
precision highp samplerBuffer;
|
precision highp samplerBuffer;
|
||||||
#else
|
#else
|
||||||
|
precision mediump int;
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
precision mediump samplerBuffer;
|
precision mediump samplerBuffer;
|
||||||
#endif // GL_FRAGMENT_PRECISION_HIGH
|
#endif // GL_FRAGMENT_PRECISION_HIGH
|
||||||
|
|
|
@ -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
|
|
@ -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
|
Reference in New Issue