From 6bc085112177f32fc40b236bef0cd478ec4af784 Mon Sep 17 00:00:00 2001 From: Michael Rennie Date: Fri, 24 Mar 2017 11:12:57 +0000 Subject: [PATCH] On glDrawArrays, copy client vertex arrays into temporary VBOs. GLES allows drawing from client memory instead of VBOs. We keep a set of buffers, one for each potential attribute, to bind the vertex attribute to instead of the client array pointer. These buffers are then filled before the draw call. --- renderdoc/driver/gl/gl_common.cpp | 18 +++++ renderdoc/driver/gl/gl_common.h | 2 + renderdoc/driver/gl/gl_driver.cpp | 9 +++ renderdoc/driver/gl/gl_driver.h | 20 ++++++ renderdoc/driver/gl/gl_renderstate.cpp | 15 +--- .../driver/gl/wrappers/gl_draw_funcs.cpp | 71 +++++++++++++++++++ 6 files changed, 121 insertions(+), 14 deletions(-) diff --git a/renderdoc/driver/gl/gl_common.cpp b/renderdoc/driver/gl/gl_common.cpp index 58b8d2421..640ffd6e2 100644 --- a/renderdoc/driver/gl/gl_common.cpp +++ b/renderdoc/driver/gl/gl_common.cpp @@ -823,6 +823,24 @@ GLenum QueryEnum(size_t idx) return eGL_NONE; } +size_t GLTypeSize(GLenum type) +{ + switch(type) + { + case eGL_UNSIGNED_BYTE: + case eGL_BYTE: return 1; + case eGL_UNSIGNED_SHORT: + case eGL_SHORT: + case eGL_HALF_FLOAT: return 2; + case eGL_UNSIGNED_INT: + case eGL_INT: + case eGL_FLOAT: return 4; + case eGL_DOUBLE: return 8; + default: RDCWARN("Unhandled element type %s", ToStr::Get(type).c_str()); + } + return 0; +} + size_t ShaderIdx(GLenum buf) { switch(buf) diff --git a/renderdoc/driver/gl/gl_common.h b/renderdoc/driver/gl/gl_common.h index 156a0cb92..c4363651d 100644 --- a/renderdoc/driver/gl/gl_common.h +++ b/renderdoc/driver/gl/gl_common.h @@ -236,6 +236,8 @@ struct GLMarkerRegion static const GLHookSet *gl; }; +size_t GLTypeSize(GLenum type); + size_t BufferIdx(GLenum buf); GLenum BufferEnum(size_t idx); diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index 76ca36116..7d79b8327 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -1452,6 +1452,15 @@ void WrappedOpenGL::ActivateContext(GLWindowingData winData) m_Internal = m_Real; glEmulate::EmulateRequiredExtensions(&m_Real, &m_Internal); + + // Initialize VBOs used in case we copy from client memory. + glGenBuffers(ARRAY_COUNT(ctxdata.m_ClientMemoryVBOs), ctxdata.m_ClientMemoryVBOs); + for(size_t i = 0; i < ARRAY_COUNT(ctxdata.m_ClientMemoryVBOs); i++) + { + glBindBuffer(eGL_ARRAY_BUFFER, ctxdata.m_ClientMemoryVBOs[i]); + glBufferData(eGL_ARRAY_BUFFER, 64, NULL, eGL_DYNAMIC_DRAW); + } + glBindBuffer(eGL_ARRAY_BUFFER, 0); } } } diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index 1214f28e4..3902c5fa0 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -442,8 +442,28 @@ private: GLuint m_Program; GLResourceRecord *GetActiveTexRecord() { return m_TextureRecord[m_TextureUnit]; } + // GLES allows drawing from client memory, in which case we will copy to + // temporary VBOs so that input mesh data is recorded. See struct ClientMemoryData + GLuint m_ClientMemoryVBOs[16]; }; + struct ClientMemoryData + { + struct VertexAttrib + { + GLuint index; + GLint size; + GLenum type; + GLboolean normalized; + GLsizei stride; + void *pointer; + }; + std::vector attribs; + GLuint prevArrayBufferBinding; + }; + ClientMemoryData *CopyClientMemoryArrays(GLint first, GLsizei count); + void RestoreClientMemoryArrays(ClientMemoryData *clientMemoryArrays); + map m_ContextData; ContextData &GetCtxData(); diff --git a/renderdoc/driver/gl/gl_renderstate.cpp b/renderdoc/driver/gl/gl_renderstate.cpp index 37b4ecef1..50f79534a 100644 --- a/renderdoc/driver/gl/gl_renderstate.cpp +++ b/renderdoc/driver/gl/gl_renderstate.cpp @@ -181,20 +181,7 @@ byte *PixelUnpackState::Unpack(byte *pixels, GLsizei width, GLsizei height, GLsi size_t destrowstride = pixelSize * width; size_t destimgstride = destrowstride * height; - size_t elemSize = 1; - switch(basetype) - { - case eGL_UNSIGNED_BYTE: - case eGL_BYTE: elemSize = 1; break; - case eGL_UNSIGNED_SHORT: - case eGL_SHORT: - case eGL_HALF_FLOAT: elemSize = 2; break; - case eGL_UNSIGNED_INT: - case eGL_INT: - case eGL_FLOAT: elemSize = 4; break; - case eGL_DOUBLE: elemSize = 8; break; - default: break; - } + size_t elemSize = GLTypeSize(basetype); size_t allocsize = width * RDCMAX(1, height) * RDCMAX(1, depth) * pixelSize; byte *ret = new byte[allocsize]; diff --git a/renderdoc/driver/gl/wrappers/gl_draw_funcs.cpp b/renderdoc/driver/gl/wrappers/gl_draw_funcs.cpp index 17bea32d1..66fe8f18f 100644 --- a/renderdoc/driver/gl/wrappers/gl_draw_funcs.cpp +++ b/renderdoc/driver/gl/wrappers/gl_draw_funcs.cpp @@ -709,10 +709,75 @@ bool WrappedOpenGL::Serialise_glDrawArrays(GLenum mode, GLint first, GLsizei cou return true; } +WrappedOpenGL::ClientMemoryData *WrappedOpenGL::CopyClientMemoryArrays(GLint first, GLsizei count) +{ + ContextData &cd = GetCtxData(); + GLResourceRecord *varecord = cd.m_VertexArrayRecord; + if(m_State != WRITING_CAPFRAME || varecord) // Early out if VAO bound, as VAOs are VBO-only. + return NULL; + + ClientMemoryData *clientMemory = new ClientMemoryData; + m_Real.glGetIntegerv(eGL_ARRAY_BUFFER_BINDING, (GLint *)&clientMemory->prevArrayBufferBinding); + + for(GLuint i = 0; i < ARRAY_COUNT(cd.m_ClientMemoryVBOs); i++) + { + GLint enabled = 0; + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled); + if(!enabled) + continue; + + // Check that the attrib is using client-memory. + GLuint buffer; + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint *)&buffer); + if(buffer != 0) + continue; + + // App initially used client memory, so copy it into the temporary buffer. + ClientMemoryData::VertexAttrib attrib; + memset(&attrib, 0, sizeof(attrib)); + attrib.index = i; + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_SIZE, &attrib.size); + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_TYPE, (GLint *)&attrib.type); + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_NORMALIZED, (GLint *)&attrib.normalized); + m_Real.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_STRIDE, &attrib.stride); + m_Real.glGetVertexAttribPointerv(i, eGL_VERTEX_ATTRIB_ARRAY_POINTER, &attrib.pointer); + + GLint totalStride = attrib.stride ? attrib.stride : (GLint)GLTypeSize(attrib.type) * attrib.size; + glBindBuffer(eGL_ARRAY_BUFFER, cd.m_ClientMemoryVBOs[i]); + // Copy all client memory, and the pointer becomes a zero offset. + glBufferData(eGL_ARRAY_BUFFER, (first + count) * totalStride, attrib.pointer, eGL_STATIC_DRAW); + glVertexAttribPointer(attrib.index, attrib.size, attrib.type, attrib.normalized, attrib.stride, + NULL); + + clientMemory->attribs.push_back(attrib); + } + + return clientMemory; +} + +void WrappedOpenGL::RestoreClientMemoryArrays(ClientMemoryData *clientMemoryArrays) +{ + if(!clientMemoryArrays) + return; + + // Restore the 0-buffer bindings and attrib pointers. + glBindBuffer(eGL_ARRAY_BUFFER, 0); + for(const ClientMemoryData::VertexAttrib &attrib : clientMemoryArrays->attribs) + { + glVertexAttribPointer(attrib.index, attrib.size, attrib.type, attrib.normalized, attrib.stride, + attrib.pointer); + } + glBindBuffer(eGL_ARRAY_BUFFER, clientMemoryArrays->prevArrayBufferBinding); + + delete clientMemoryArrays; +} + void WrappedOpenGL::glDrawArrays(GLenum mode, GLint first, GLsizei count) { CoherentMapImplicitBarrier(); + ClientMemoryData *clientMemory = CopyClientMemoryArrays(first, count); + m_Real.glDrawArrays(mode, first, count); if(m_State == WRITING_CAPFRAME) @@ -731,6 +796,8 @@ void WrappedOpenGL::glDrawArrays(GLenum mode, GLint first, GLsizei count) GLRenderState state(&m_Real, m_pSerialiser, m_State); state.MarkDirty(this); } + + RestoreClientMemoryArrays(clientMemory); } bool WrappedOpenGL::Serialise_glDrawArraysIndirect(GLenum mode, const void *indirect) @@ -849,6 +916,8 @@ void WrappedOpenGL::glDrawArraysInstanced(GLenum mode, GLint first, GLsizei coun { CoherentMapImplicitBarrier(); + ClientMemoryData *clientMemory = CopyClientMemoryArrays(first, count); + m_Real.glDrawArraysInstanced(mode, first, count, instancecount); if(m_State == WRITING_CAPFRAME) @@ -867,6 +936,8 @@ void WrappedOpenGL::glDrawArraysInstanced(GLenum mode, GLint first, GLsizei coun GLRenderState state(&m_Real, m_pSerialiser, m_State); state.MarkDirty(this); } + + RestoreClientMemoryArrays(clientMemory); } bool WrappedOpenGL::Serialise_glDrawArraysInstancedBaseInstance(GLenum mode, GLint first,