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/renderer_opengl.cpp
|
||||
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.h
|
||||
renderer_opengl/texture_filters/bicubic/bicubic.cpp
|
||||
|
@ -99,6 +101,9 @@ add_library(video_core STATIC
|
|||
)
|
||||
|
||||
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/x_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_state.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/utils.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
|
||||
}};
|
||||
|
||||
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 SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
|
||||
if (type == SurfaceType::Color) {
|
||||
|
@ -87,79 +81,6 @@ const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
|
|||
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>
|
||||
static constexpr auto RangeFromInterval(Map& map, const Interval& 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);
|
||||
std::vector<u8> decoded_texture;
|
||||
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
|
||||
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
|
||||
higher quality 256x256 texture. If the 256x256 texture is displayed first and the 32x32
|
||||
texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture will
|
||||
appear in the corner of the 256x256 texture.
|
||||
If texture dumping is enabled and the 32x32 is undumped, Citra will attempt to dump it.
|
||||
Since the underlying OpenGL texture is still 256x256, Citra crashes because it thinks the
|
||||
texture is only 32x32.
|
||||
higher quality 256x256 texture. If the 256x256 texture is displayed first and the
|
||||
32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture
|
||||
will appear in the corner of the 256x256 texture. If texture dumping is enabled and
|
||||
the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL
|
||||
texture is still 256x256, Citra crashes because it thinks the texture is only 32x32.
|
||||
GetTexImageOES conveniently only dumps the specified region, and works on both
|
||||
desktop and ES.
|
||||
*/
|
||||
GetTexImageOES(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, height, width, 0,
|
||||
&decoded_texture[0], decoded_texture.size());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
// if the backend isn't OpenGL ES, this won't be initialized yet
|
||||
if (!owner.texture_downloader_es)
|
||||
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);
|
||||
if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height))
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save decoded texture");
|
||||
|
@ -905,14 +831,6 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
|
|||
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);
|
||||
|
||||
if (gl_buffer.empty()) {
|
||||
|
@ -950,9 +868,9 @@ void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint
|
|||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if (GLES) {
|
||||
GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, rect.GetHeight(),
|
||||
rect.GetWidth(), 0, &gl_buffer[buffer_offset],
|
||||
gl_buffer.size() - buffer_offset);
|
||||
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type,
|
||||
rect.GetHeight(), rect.GetWidth(),
|
||||
&gl_buffer[buffer_offset]);
|
||||
} else {
|
||||
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,
|
||||
resolution_scale_factor);
|
||||
format_reinterpreter = std::make_unique<FormatReinterpreterOpenGL>();
|
||||
if (GLES)
|
||||
texture_downloader_es = std::make_unique<TextureDownloaderES>(false);
|
||||
|
||||
read_framebuffer.Create();
|
||||
draw_framebuffer.Create();
|
||||
|
|
|
@ -171,6 +171,8 @@ private:
|
|||
bool valid = false;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL;
|
||||
|
||||
struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> {
|
||||
CachedSurface(RasterizerCacheOpenGL& owner) : owner{owner} {}
|
||||
~CachedSurface();
|
||||
|
@ -267,6 +269,15 @@ struct CachedTextureCube {
|
|||
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 {
|
||||
public:
|
||||
RasterizerCacheOpenGL();
|
||||
|
@ -373,6 +384,7 @@ public:
|
|||
|
||||
std::unique_ptr<TextureFilterer> texture_filterer;
|
||||
std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter;
|
||||
std::unique_ptr<TextureDownloaderES> texture_downloader_es;
|
||||
};
|
||||
|
||||
} // 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.
|
||||
static constexpr char fragment_shader_precision_OES[] = R"(
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp float;
|
||||
precision highp samplerBuffer;
|
||||
#else
|
||||
precision mediump float;
|
||||
precision mediump int;
|
||||
precision mediump float;
|
||||
precision mediump samplerBuffer;
|
||||
#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…
Reference in a new issue