mirror of
https://github.com/libretro/ppsspp.git
synced 2024-12-03 14:40:49 +00:00
GL render manager: Simple implementation of synchronous framebuffer readbacks.
This commit is contained in:
parent
2b12776137
commit
49c3cb83fe
@ -832,7 +832,11 @@ endif()
|
||||
|
||||
set(THIN3D_PLATFORMS ext/native/thin3d/thin3d_gl.cpp
|
||||
ext/native/thin3d/GLRenderManager.cpp
|
||||
ext/native/thin3d/GLQueueRunner.cpp)
|
||||
ext/native/thin3d/GLRenderManager.h
|
||||
ext/native/thin3d/GLQueueRunner.cpp
|
||||
ext/native/thin3d/GLQueueRunner.h
|
||||
ext/native/thin3d/DataFormatGL.cpp
|
||||
ext/native/thin3d/DataFormatGL.h)
|
||||
|
||||
set(THIN3D_PLATFORMS ${THIN3D_PLATFORMS}
|
||||
ext/native/thin3d/thin3d_vulkan.cpp
|
||||
|
@ -83,8 +83,11 @@ LOCAL_SRC_FILES :=\
|
||||
thin3d/thin3d.cpp \
|
||||
thin3d/thin3d_gl.cpp \
|
||||
thin3d/thin3d_vulkan.cpp \
|
||||
thin3d/GLRenderManager.cpp \
|
||||
thin3d/GLQueueRunner.cpp \
|
||||
thin3d/VulkanRenderManager.cpp \
|
||||
thin3d/VulkanQueueRunner.cpp \
|
||||
thin3d/DataFormatGL.cpp \
|
||||
ui/view.cpp \
|
||||
ui/viewgroup.cpp \
|
||||
ui/ui.cpp \
|
||||
|
@ -240,6 +240,7 @@
|
||||
<ClInclude Include="gfx_es2\draw_text_win.h" />
|
||||
<ClInclude Include="thin3d\d3d11_loader.h" />
|
||||
<ClInclude Include="thin3d\DataFormat.h" />
|
||||
<ClInclude Include="thin3d\DataFormatGL.h" />
|
||||
<ClInclude Include="thin3d\GLQueueRunner.h" />
|
||||
<ClInclude Include="thin3d\GLRenderManager.h" />
|
||||
<ClInclude Include="thin3d\VulkanQueueRunner.h" />
|
||||
@ -699,6 +700,7 @@
|
||||
<ClCompile Include="gfx_es2\draw_text_win.cpp" />
|
||||
<ClCompile Include="math\dataconv.cpp" />
|
||||
<ClCompile Include="thin3d\d3d11_loader.cpp" />
|
||||
<ClCompile Include="thin3d\DataFormatGL.cpp" />
|
||||
<ClCompile Include="thin3d\GLQueueRunner.cpp" />
|
||||
<ClCompile Include="thin3d\GLRenderManager.cpp" />
|
||||
<ClCompile Include="thin3d\thin3d_d3d11.cpp" />
|
||||
|
@ -335,6 +335,9 @@
|
||||
<ClInclude Include="thin3d\GLQueueRunner.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="thin3d\DataFormatGL.h">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="gfx\gl_debug_log.cpp">
|
||||
@ -805,6 +808,9 @@
|
||||
<ClCompile Include="thin3d\GLRenderManager.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="thin3d\DataFormatGL.cpp">
|
||||
<Filter>thin3d</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="gfx">
|
||||
|
90
ext/native/thin3d/DataFormatGL.cpp
Normal file
90
ext/native/thin3d/DataFormatGL.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include "thin3d/DataFormatGL.h"
|
||||
#include "base/logging.h"
|
||||
#include "Common/Log.h"
|
||||
|
||||
namespace Draw {
|
||||
|
||||
// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage.
|
||||
bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
|
||||
alignment = 4;
|
||||
switch (fmt) {
|
||||
case DataFormat::R8G8B8A8_UNORM:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case DataFormat::D32F:
|
||||
internalFormat = GL_DEPTH_COMPONENT;
|
||||
format = GL_DEPTH_COMPONENT;
|
||||
type = GL_FLOAT;
|
||||
break;
|
||||
|
||||
#ifndef USING_GLES2
|
||||
case DataFormat::S8:
|
||||
internalFormat = GL_STENCIL_INDEX;
|
||||
format = GL_STENCIL_INDEX;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
alignment = 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case DataFormat::R8G8B8_UNORM:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
alignment = 1;
|
||||
break;
|
||||
|
||||
case DataFormat::B4G4R4A4_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_4_4_4_4;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::B5G6R5_UNORM_PACK16:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_SHORT_5_6_5;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::B5G5R5A1_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_5_5_5_1;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
#ifndef USING_GLES2
|
||||
case DataFormat::A4R4G4B4_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::R5G6B5_UNORM_PACK16:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_SHORT_5_6_5_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::A1R5G5B5_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
_assert_msg_(G3D, false, "Thin3d GL: Unsupported texture format %d", (int)fmt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
10
ext/native/thin3d/DataFormatGL.h
Normal file
10
ext/native/thin3d/DataFormatGL.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "gfx/gl_common.h"
|
||||
#include "thin3d/DataFormat.h"
|
||||
|
||||
namespace Draw {
|
||||
|
||||
bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment);
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#include "GLQueueRunner.h"
|
||||
#include "GLRenderManager.h"
|
||||
#include "DataFormatGL.h"
|
||||
#include "base/logging.h"
|
||||
#include "gfx/gl_common.h"
|
||||
#include "gfx/gl_debug_log.h"
|
||||
@ -789,6 +790,10 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) {
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
_dbg_assert_(G3D, srcTex);
|
||||
_dbg_assert_(G3D, dstTex);
|
||||
|
||||
#if defined(USING_GLES2)
|
||||
#ifndef IOS
|
||||
glCopyImageSubDataOES(
|
||||
@ -813,7 +818,49 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) {
|
||||
}
|
||||
|
||||
void GLQueueRunner::PerformReadback(const GLRStep &pass) {
|
||||
GLRFramebuffer *fb = pass.readback.src;
|
||||
|
||||
fbo_bind_fb_target(true, fb ? fb->handle : 0);
|
||||
|
||||
// Reads from the "bound for read" framebuffer.
|
||||
if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
|
||||
GLuint internalFormat;
|
||||
GLuint format;
|
||||
GLuint type;
|
||||
int alignment;
|
||||
if (!Draw::Thin3DFormatToFormatAndType(pass.readback.dstFormat, internalFormat, format, type, alignment)) {
|
||||
assert(false);
|
||||
}
|
||||
int pixelStride = pass.readback.srcRect.w;
|
||||
// Apply the correct alignment.
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, alignment);
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
// Some drivers seem to require we specify this. See #8254.
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride);
|
||||
}
|
||||
|
||||
GLRect2D rect = pass.readback.srcRect;
|
||||
|
||||
int size = alignment * rect.w * rect.h;
|
||||
if (size > readbackBufferSize_) {
|
||||
delete[] readbackBuffer_;
|
||||
readbackBuffer_ = new uint8_t[size];
|
||||
readbackBufferSize_ = size;
|
||||
}
|
||||
|
||||
glReadPixels(rect.x, rect.y, rect.w, rect.h, format, type, readbackBuffer_);
|
||||
|
||||
#ifdef DEBUG_READ_PIXELS
|
||||
LogReadPixelsError(glGetError());
|
||||
#endif
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
}
|
||||
|
||||
void GLQueueRunner::PerformReadbackImage(const GLRStep &pass) {
|
||||
@ -841,7 +888,12 @@ void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) {
|
||||
}
|
||||
|
||||
void GLQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) {
|
||||
|
||||
// TODO: Maybe move data format conversion here, and always read back 8888. Drivers
|
||||
// don't usually provide very optimized implementations.
|
||||
int bpp = Draw::DataFormatSizeInBytes(destFormat);
|
||||
for (int y = 0; y < height; y++) {
|
||||
memcpy(pixels + y * pixelStride * bpp, readbackBuffer_ + y * width * bpp, pixelStride * bpp);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint GLQueueRunner::AllocTextureName() {
|
||||
|
@ -289,6 +289,7 @@ struct GLRStep {
|
||||
int aspectMask;
|
||||
GLRFramebuffer *src;
|
||||
GLRect2D srcRect;
|
||||
Draw::DataFormat dstFormat;
|
||||
} readback;
|
||||
struct {
|
||||
GLint texture;
|
||||
@ -354,7 +355,7 @@ private:
|
||||
|
||||
// Readback buffer. Currently we only support synchronous readback, so we only really need one.
|
||||
// We size it generously.
|
||||
GLint readbackBuffer_ = 0;
|
||||
uint8_t *readbackBuffer_ = nullptr;
|
||||
int readbackBufferSize_ = 0;
|
||||
|
||||
float maxAnisotropyLevel_ = 0.0f;
|
||||
|
@ -215,6 +215,33 @@ void GLRenderManager::BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLR
|
||||
steps_.push_back(step);
|
||||
}
|
||||
|
||||
bool GLRenderManager::CopyFramebufferToMemorySync(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) {
|
||||
GLRStep *step = new GLRStep{ GLRStepType::READBACK };
|
||||
step->readback.src = src;
|
||||
step->readback.srcRect = { x, y, w, h };
|
||||
step->readback.aspectMask = aspectBits;
|
||||
step->readback.dstFormat = destFormat;
|
||||
steps_.push_back(step);
|
||||
|
||||
curRenderStep_ = nullptr;
|
||||
FlushSync();
|
||||
|
||||
Draw::DataFormat srcFormat;
|
||||
if (aspectBits & GL_COLOR_BUFFER_BIT) {
|
||||
srcFormat = Draw::DataFormat::R8G8B8A8_UNORM;
|
||||
} else if (aspectBits & GL_STENCIL_BUFFER_BIT) {
|
||||
// Copies from stencil are always S8.
|
||||
srcFormat = Draw::DataFormat::S8;
|
||||
} else if (aspectBits & GL_DEPTH_BUFFER_BIT) {
|
||||
// TODO: Do this properly.
|
||||
srcFormat = Draw::DataFormat::D24_S8;
|
||||
} else {
|
||||
_assert_(false);
|
||||
}
|
||||
queueRunner_.CopyReadbackBuffer(w, h, srcFormat, destFormat, pixelStride, pixels);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLRenderManager::BeginFrame() {
|
||||
VLOG("BeginFrame");
|
||||
|
||||
@ -255,14 +282,18 @@ void GLRenderManager::Finish() {
|
||||
FrameData &frameData = frameData_[curFrame];
|
||||
if (!useThread_) {
|
||||
frameData.steps = std::move(steps_);
|
||||
steps_.clear();
|
||||
frameData.initSteps = std::move(initSteps_);
|
||||
initSteps_.clear();
|
||||
frameData.type = GLRRunType::END;
|
||||
Run(curFrame);
|
||||
} else {
|
||||
std::unique_lock<std::mutex> lock(frameData.pull_mutex);
|
||||
VLOG("PUSH: Frame[%d].readyForRun = true", curFrame);
|
||||
frameData.steps = std::move(steps_);
|
||||
steps_.clear();
|
||||
frameData.initSteps = std::move(initSteps_);
|
||||
initSteps_.clear();
|
||||
frameData.readyForRun = true;
|
||||
frameData.type = GLRRunType::END;
|
||||
frameData.pull_condVar.notify_all();
|
||||
@ -340,6 +371,41 @@ void GLRenderManager::Run(int frame) {
|
||||
VLOG("PULL: Finished running frame %d", frame);
|
||||
}
|
||||
|
||||
void GLRenderManager::FlushSync() {
|
||||
// TODO: Reset curRenderStep_?
|
||||
int curFrame = curFrame_;
|
||||
FrameData &frameData = frameData_[curFrame];
|
||||
if (!useThread_) {
|
||||
frameData.initSteps = std::move(initSteps_);
|
||||
initSteps_.clear();
|
||||
frameData.steps = std::move(steps_);
|
||||
steps_.clear();
|
||||
frameData.type = GLRRunType::SYNC;
|
||||
Run(curFrame);
|
||||
} else {
|
||||
std::unique_lock<std::mutex> lock(frameData.pull_mutex);
|
||||
VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame);
|
||||
frameData.initSteps = std::move(initSteps_);
|
||||
initSteps_.clear();
|
||||
frameData.steps = std::move(steps_);
|
||||
steps_.clear();
|
||||
frameData.readyForRun = true;
|
||||
assert(frameData.readyForFence == false);
|
||||
frameData.type = GLRRunType::SYNC;
|
||||
frameData.pull_condVar.notify_all();
|
||||
}
|
||||
|
||||
if (useThread_) {
|
||||
std::unique_lock<std::mutex> lock(frameData.push_mutex);
|
||||
// Wait for the flush to be hit, since we're syncing.
|
||||
while (!frameData.readyForFence) {
|
||||
VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame);
|
||||
frameData.push_condVar.wait(lock);
|
||||
}
|
||||
frameData.readyForFence = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GLRenderManager::EndSyncFrame(int frame) {
|
||||
FrameData &frameData = frameData_[frame];
|
||||
Submit(frame, false);
|
||||
@ -347,7 +413,6 @@ void GLRenderManager::EndSyncFrame(int frame) {
|
||||
// This is brutal! Should probably wait for a fence instead, not that it'll matter much since we'll
|
||||
// still stall everything.
|
||||
glFinish();
|
||||
// vkDeviceWaitIdle(vulkan_->GetDevice());
|
||||
|
||||
// At this point we can resume filling the command buffers for the current frame since
|
||||
// we know the device is idle - and thus all previously enqueued command buffers have been processed.
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "math/dataconv.h"
|
||||
#include "math/lin/matrix4x4.h"
|
||||
#include "thin3d/thin3d.h"
|
||||
#include "thin3d/DataFormatGL.h"
|
||||
#include "gfx/gl_common.h"
|
||||
#include "gfx/gl_debug_log.h"
|
||||
#include "gfx_es2/gpu_features.h"
|
||||
@ -662,90 +663,6 @@ public:
|
||||
FBColorDepth colorDepth;
|
||||
};
|
||||
|
||||
|
||||
// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage.
|
||||
static bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
|
||||
alignment = 4;
|
||||
switch (fmt) {
|
||||
case DataFormat::R8G8B8A8_UNORM:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
|
||||
case DataFormat::D32F:
|
||||
internalFormat = GL_DEPTH_COMPONENT;
|
||||
format = GL_DEPTH_COMPONENT;
|
||||
type = GL_FLOAT;
|
||||
break;
|
||||
|
||||
#ifndef USING_GLES2
|
||||
case DataFormat::S8:
|
||||
internalFormat = GL_STENCIL_INDEX;
|
||||
format = GL_STENCIL_INDEX;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
alignment = 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case DataFormat::R8G8B8_UNORM:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
alignment = 1;
|
||||
break;
|
||||
|
||||
case DataFormat::B4G4R4A4_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_4_4_4_4;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::B5G6R5_UNORM_PACK16:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_SHORT_5_6_5;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::B5G5R5A1_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_5_5_5_1;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
#ifndef USING_GLES2
|
||||
case DataFormat::A4R4G4B4_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_4_4_4_4_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::R5G6B5_UNORM_PACK16:
|
||||
internalFormat = GL_RGB;
|
||||
format = GL_RGB;
|
||||
type = GL_UNSIGNED_SHORT_5_6_5_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
|
||||
case DataFormat::A1R5G5B5_UNORM_PACK16:
|
||||
internalFormat = GL_RGBA;
|
||||
format = GL_RGBA;
|
||||
type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
alignment = 2;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
ELOG("Thin3d GL: Unsupported texture format %d", (int)fmt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) {
|
||||
if (width != width_ || height != height_ || depth != depth_) {
|
||||
// When switching to texStorage we need to handle this correctly.
|
||||
@ -811,39 +728,14 @@ static void LogReadPixelsError(GLenum error) {
|
||||
|
||||
bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat dataFormat, void *pixels, int pixelStride) {
|
||||
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)src;
|
||||
/*
|
||||
fbo_bind_fb_target(true, fb ? fb->handle : 0);
|
||||
|
||||
// Reads from the "bound for read" framebuffer.
|
||||
if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
|
||||
GLuint internalFormat;
|
||||
GLuint format;
|
||||
GLuint type;
|
||||
int alignment;
|
||||
if (!Thin3DFormatToFormatAndType(dataFormat, internalFormat, format, type, alignment)) {
|
||||
assert(false);
|
||||
}
|
||||
// Apply the correct alignment.
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, alignment);
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
// Even if not required, some drivers seem to require we specify this. See #8254.
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride);
|
||||
}
|
||||
|
||||
glReadPixels(x, y, w, h, format, type, pixels);
|
||||
#ifdef DEBUG_READ_PIXELS
|
||||
LogReadPixelsError(glGetError());
|
||||
#endif
|
||||
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
*/
|
||||
GLuint aspect = 0;
|
||||
if (channelBits & FB_COLOR_BIT)
|
||||
aspect |= GL_COLOR_BUFFER_BIT;
|
||||
if (channelBits & FB_DEPTH_BIT)
|
||||
aspect |= GL_DEPTH_BUFFER_BIT;
|
||||
if (channelBits & FB_STENCIL_BIT)
|
||||
aspect |= GL_STENCIL_BUFFER_BIT;
|
||||
renderManager_.CopyFramebufferToMemorySync(fb->framebuffer, aspect, x, y, w, h, dataFormat, (uint8_t *)pixels, pixelStride);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user