mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-14 22:10:43 +00:00
584d439620
* To align a buffer inside a chunk to a wider boundary like 32-bytes in this case, the chunk needs to be aligned and the buffer within it also needs to be aligned. * The utility function accounts for the buffer serialised having a uint32 length in front of it, so it pads out until that will be at the desired boundary. This is a bit of a messy solution, but the easiest way to ensure the padding is there while being easily backwards compatible with old logs without the padding. * D3D11 and GL serialise versions are bumped, D3D11 version is backwards compatible, GL breaks compatibility.
4182 lines
134 KiB
C++
4182 lines
134 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2014 Crytek
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "common/common.h"
|
|
#include "serialise/string_utils.h"
|
|
#include "../gl_driver.h"
|
|
|
|
#pragma region Buffers
|
|
|
|
bool WrappedOpenGL::Serialise_glGenBuffers(GLsizei n, GLuint* buffers)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), *buffers)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glGenBuffers(1, &real);
|
|
|
|
GLResource res = BufferRes(GetCtx(), real);
|
|
|
|
ResourceId live = m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
|
|
m_Buffers[live].resource = res;
|
|
m_Buffers[live].curType = eGL_NONE;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glGenBuffers(GLsizei n, GLuint *buffers)
|
|
{
|
|
m_Real.glGenBuffers(n, buffers);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = BufferRes(GetCtx(), buffers[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(GEN_BUFFER);
|
|
Serialise_glGenBuffers(1, buffers+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
m_Buffers[id].resource = res;
|
|
m_Buffers[id].curType = eGL_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glCreateBuffers(GLsizei n, GLuint* buffers)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), *buffers)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glCreateBuffers(1, &real);
|
|
|
|
GLResource res = BufferRes(GetCtx(), real);
|
|
|
|
ResourceId live = m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
|
|
m_Buffers[live].resource = res;
|
|
m_Buffers[live].curType = eGL_NONE;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glCreateBuffers(GLsizei n, GLuint *buffers)
|
|
{
|
|
m_Real.glCreateBuffers(n, buffers);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = BufferRes(GetCtx(), buffers[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(CREATE_BUFFER);
|
|
Serialise_glCreateBuffers(1, buffers+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
m_Buffers[id].resource = res;
|
|
m_Buffers[id].curType = eGL_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindBuffer(GLenum target, GLuint buffer)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(ResourceId, Id, (buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId()));
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
if(Id != ResourceId())
|
|
GetResourceManager()->GetResourceRecord(Id)->datatype = Target;
|
|
}
|
|
else if(m_State < WRITING)
|
|
{
|
|
if(Id == ResourceId())
|
|
{
|
|
m_Real.glBindBuffer(Target, 0);
|
|
}
|
|
else
|
|
{
|
|
// if we're just reading, make sure not to trample state (e.g. element array buffer
|
|
// binding in a VAO), since this is just a bind-to-create chunk.
|
|
GLuint prevbuf = 0;
|
|
if(m_State == READING && m_CurEventID == 0)
|
|
m_Real.glGetIntegerv(BufferBinding(Target), (GLint *)&prevbuf);
|
|
|
|
GLResource res = GetResourceManager()->GetLiveResource(Id);
|
|
m_Real.glBindBuffer(Target, res.name);
|
|
|
|
m_Buffers[GetResourceManager()->GetLiveID(Id)].curType = Target;
|
|
|
|
if(m_State == READING && m_CurEventID == 0)
|
|
m_Real.glBindBuffer(Target, prevbuf);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindBuffer(GLenum target, GLuint buffer)
|
|
{
|
|
m_Real.glBindBuffer(target, buffer);
|
|
|
|
ContextData &cd = GetCtxData();
|
|
|
|
size_t idx = BufferIdx(target);
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
if(buffer == 0)
|
|
cd.m_BufferRecord[idx] = NULL;
|
|
else
|
|
cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffer);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
if(buffer)
|
|
{
|
|
FrameRefType refType = eFrameRef_Read;
|
|
|
|
// these targets write to the buffer
|
|
if(target == eGL_ATOMIC_COUNTER_BUFFER ||
|
|
target == eGL_COPY_WRITE_BUFFER ||
|
|
target == eGL_PIXEL_PACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_TRANSFORM_FEEDBACK_BUFFER)
|
|
refType = eFrameRef_Write;
|
|
|
|
GetResourceManager()->MarkResourceFrameReferenced(cd.m_BufferRecord[idx]->GetResourceID(), refType);
|
|
}
|
|
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
|
|
if(buffer == 0)
|
|
{
|
|
cd.m_BufferRecord[idx] = NULL;
|
|
return;
|
|
}
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *r = cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
// it's legal to re-type buffers, generate another BindBuffer chunk to rename
|
|
if(r->datatype != target)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
r->LockChunks();
|
|
while(true)
|
|
{
|
|
Chunk *end = r->GetLastChunk();
|
|
|
|
if(end->GetChunkType() == BIND_BUFFER)
|
|
{
|
|
SAFE_DELETE(end);
|
|
|
|
r->PopChunk();
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
r->UnlockChunks();
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffer);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
r->AddChunk(chunk);
|
|
}
|
|
|
|
// element array buffer binding is vertex array record state, record there (if we've not just stopped)
|
|
if(m_State == WRITING_IDLE && target == eGL_ELEMENT_ARRAY_BUFFER && RecordUpdateCheck(cd.m_VertexArrayRecord))
|
|
{
|
|
GLuint vao = cd.m_VertexArrayRecord->Resource.name;
|
|
|
|
// use glVertexArrayElementBuffer to ensure the vertex array is bound when we bind the
|
|
// element buffer
|
|
SCOPED_SERIALISE_CONTEXT(VAO_ELEMENT_BUFFER);
|
|
Serialise_glVertexArrayElementBuffer(vao, buffer);
|
|
|
|
cd.m_VertexArrayRecord->AddChunk(scope.Get());
|
|
|
|
cd.m_VertexArrayRecord->AddParent(r);
|
|
}
|
|
|
|
// store as transform feedback record state
|
|
if(m_State == WRITING_IDLE && target == eGL_TRANSFORM_FEEDBACK_BUFFER && RecordUpdateCheck(cd.m_FeedbackRecord))
|
|
{
|
|
GLuint feedback = cd.m_FeedbackRecord->Resource.name;
|
|
|
|
// use glTransformFeedbackBufferBase to ensure the feedback object is bound when we bind the
|
|
// buffer
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_BASE);
|
|
Serialise_glTransformFeedbackBufferBase(feedback, 0, buffer);
|
|
|
|
cd.m_FeedbackRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
// immediately consider buffers bound to transform feedbacks/SSBOs/atomic counters as dirty
|
|
if(m_State == WRITING_IDLE &&
|
|
(target == eGL_TRANSFORM_FEEDBACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_ATOMIC_COUNTER_BUFFER)
|
|
)
|
|
{
|
|
GetResourceManager()->MarkDirtyResource(r->GetResourceID());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Buffers[GetResourceManager()->GetID(BufferRes(GetCtx(), buffer))].curType = target;
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glNamedBufferStorageEXT(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)));
|
|
SERIALISE_ELEMENT(uint64_t, Bytesize, (uint64_t)size);
|
|
|
|
// for satisfying GL_MIN_MAP_BUFFER_ALIGNMENT
|
|
m_pSerialiser->AlignNextBuffer(64);
|
|
|
|
SERIALISE_ELEMENT_BUF(byte *, bytes, data, (size_t)Bytesize);
|
|
|
|
uint64_t offs = m_pSerialiser->GetOffset();
|
|
|
|
SERIALISE_ELEMENT(uint32_t, Flags, flags);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glNamedBufferStorageEXT(res.name, (GLsizeiptr)Bytesize, bytes, Flags);
|
|
|
|
m_Buffers[GetResourceManager()->GetLiveID(id)].size = Bytesize;
|
|
|
|
SAFE_DELETE_ARRAY(bytes);
|
|
}
|
|
else if(m_State >= WRITING)
|
|
{
|
|
GetResourceManager()->GetResourceRecord(id)->SetDataOffset(offs - Bytesize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::Common_glNamedBufferStorageEXT(ResourceId id, GLsizeiptr size, const void *data, GLbitfield flags)
|
|
{
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
SCOPED_SERIALISE_CONTEXT(BUFFERSTORAGE);
|
|
Serialise_glNamedBufferStorageEXT(record->Resource.name, size, data, flags);
|
|
|
|
// for satisfying GL_MIN_MAP_BUFFER_ALIGNMENT
|
|
scope.SetAlignment(64);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
{
|
|
record->AddChunk(chunk);
|
|
record->SetDataPtr(chunk->GetData());
|
|
record->Length = (int32_t)size;
|
|
}
|
|
|
|
// We immediately map the whole range with appropriate flags, to be copied into whenever we
|
|
// need to propogate changes. Note: Coherent buffers are not mapped coherent, but this is
|
|
// because the user code isn't writing into them anyway and we're inserting invisible sync
|
|
// points - so there's no need for it to be coherently mapped (and there's no requirement
|
|
// that a buffer declared as coherent must ALWAYS be mapped as coherent).
|
|
if(flags & GL_MAP_PERSISTENT_BIT)
|
|
{
|
|
record->Map.persistentPtr = (byte *)m_Real.glMapNamedBufferRangeEXT(record->Resource.name, 0, size,
|
|
GL_MAP_WRITE_BIT|GL_MAP_FLUSH_EXPLICIT_BIT|GL_MAP_PERSISTENT_BIT);
|
|
RDCASSERT(record->Map.persistentPtr);
|
|
|
|
// persistent maps always need both sets of shadow storage, so allocate up front.
|
|
record->AllocShadowStorage(size, 64);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Buffers[id].size = size;
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferStorageEXT(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags)
|
|
{
|
|
byte *dummy = NULL;
|
|
|
|
if(m_State >= WRITING && data == NULL)
|
|
{
|
|
dummy = new byte[size];
|
|
memset(dummy, 0xdd, size);
|
|
data = dummy;
|
|
}
|
|
|
|
m_Real.glNamedBufferStorageEXT(buffer, size, data, flags);
|
|
|
|
Common_glNamedBufferStorageEXT(GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)), size, data, flags);
|
|
|
|
SAFE_DELETE_ARRAY(dummy);
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferStorage(GLuint buffer, GLsizei size, const void *data, GLbitfield flags)
|
|
{
|
|
// only difference to EXT function is size parameter, so just upcast
|
|
glNamedBufferStorageEXT(buffer, (GLsizeiptr)size, data, flags);
|
|
}
|
|
|
|
void WrappedOpenGL::glBufferStorage(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags)
|
|
{
|
|
byte *dummy = NULL;
|
|
|
|
if(m_State >= WRITING && data == NULL)
|
|
{
|
|
dummy = new byte[size];
|
|
memset(dummy, 0xdd, size);
|
|
data = dummy;
|
|
}
|
|
|
|
m_Real.glBufferStorage(target, size, data, flags);
|
|
|
|
if(m_State >= WRITING)
|
|
Common_glNamedBufferStorageEXT(GetCtxData().m_BufferRecord[BufferIdx(target)]->GetResourceID(), size, data, flags);
|
|
else
|
|
RDCERR("Internal buffers should be allocated via dsa interfaces");
|
|
|
|
SAFE_DELETE_ARRAY(dummy);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)));
|
|
SERIALISE_ELEMENT(uint64_t, Bytesize, (uint64_t)size);
|
|
|
|
// for satisfying GL_MIN_MAP_BUFFER_ALIGNMENT
|
|
m_pSerialiser->AlignNextBuffer(64);
|
|
|
|
SERIALISE_ELEMENT_BUF(byte *, bytes, data, (size_t)Bytesize);
|
|
|
|
if(m_State == WRITING_CAPFRAME && id.id == 22)
|
|
{
|
|
uint32_t *debug = (uint32_t *)data;
|
|
RDCLOG("Serialised glBufferData for 22: %08x %08x %08x %08x %08x %08x %08x %08x",
|
|
debug[0], debug[1], debug[2], debug[3],
|
|
debug[4], debug[5], debug[6], debug[7]);
|
|
}
|
|
|
|
uint64_t offs = m_pSerialiser->GetOffset();
|
|
|
|
SERIALISE_ELEMENT(GLenum, Usage, usage);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glNamedBufferDataEXT(res.name, (GLsizeiptr)Bytesize, bytes, Usage);
|
|
|
|
m_Buffers[GetResourceManager()->GetLiveID(id)].size = Bytesize;
|
|
|
|
SAFE_DELETE_ARRAY(bytes);
|
|
}
|
|
else if(m_State >= WRITING)
|
|
{
|
|
GetResourceManager()->GetResourceRecord(id)->SetDataOffset(offs - Bytesize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage)
|
|
{
|
|
byte *dummy = NULL;
|
|
|
|
if(m_State >= WRITING && data == NULL)
|
|
{
|
|
dummy = new byte[size];
|
|
memset(dummy, 0xdd, size);
|
|
data = dummy;
|
|
}
|
|
|
|
m_Real.glNamedBufferDataEXT(buffer, size, data, usage);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
RDCASSERT(record);
|
|
|
|
// detect buffer orphaning and just update backing store
|
|
if(m_State == WRITING_IDLE && record->GetDataPtr() != NULL && size == record->Length && usage == record->usage)
|
|
{
|
|
if(data)
|
|
memcpy(record->GetDataPtr(), data, (size_t)size);
|
|
else
|
|
memset(record->GetDataPtr(), 0xbe, (size_t)size);
|
|
return;
|
|
}
|
|
|
|
// if we're recreating the buffer, clear the record and add new chunks. Normally
|
|
// we would just mark this record as dirty and pick it up on the capture frame as initial
|
|
// data, but we don't support (if it's even possible) querying out size etc.
|
|
// we need to add only the chunks required - glGenBuffers, glBindBuffer to current target,
|
|
// and this buffer storage. All other chunks have no effect
|
|
if(m_State == WRITING_IDLE && (record->GetDataPtr() != NULL || (record->Length > 0 && size != record->Length)))
|
|
{
|
|
// we need to maintain chunk ordering, so fetch the first two chunk IDs.
|
|
// We should have at least two by this point - glGenBuffers and whatever gave the record
|
|
// a size before.
|
|
RDCASSERT(record->NumChunks() >= 2);
|
|
|
|
// remove all but the first two chunks
|
|
while(record->NumChunks() > 2)
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
int32_t id2 = record->GetLastChunkID();
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
int32_t id1 = record->GetLastChunkID();
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
RDCASSERT(!record->HasChunks());
|
|
|
|
// add glGenBuffers chunk
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(GEN_BUFFER);
|
|
Serialise_glGenBuffers(1, &buffer);
|
|
|
|
record->AddChunk(scope.Get(), id1);
|
|
}
|
|
|
|
// add glBindBuffer chunk
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(record->datatype, buffer);
|
|
|
|
record->AddChunk(scope.Get(), id2);
|
|
}
|
|
|
|
// we're about to add the buffer data chunk
|
|
}
|
|
|
|
SCOPED_SERIALISE_CONTEXT(BUFFERDATA);
|
|
Serialise_glNamedBufferDataEXT(buffer, size, data, usage);
|
|
|
|
// for satisfying GL_MIN_MAP_BUFFER_ALIGNMENT
|
|
scope.SetAlignment(64);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
// if we've already created this is a renaming/data updating call. It should go in
|
|
// the frame record so we can 'update' the buffer as it goes in the frame.
|
|
// if we haven't created the buffer at all, it could be a mid-frame create and we
|
|
// should place it in the resource record, to happen before the frame.
|
|
if(m_State == WRITING_CAPFRAME && record->GetDataPtr())
|
|
{
|
|
// we could perhaps substitute this for a 'fake' glBufferSubData chunk?
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
record->AddChunk(chunk);
|
|
record->SetDataPtr(chunk->GetData());
|
|
record->Length = (int32_t)size;
|
|
record->usage = usage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Buffers[GetResourceManager()->GetID(BufferRes(GetCtx(), buffer))].size = size;
|
|
}
|
|
|
|
SAFE_DELETE_ARRAY(dummy);
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferData(GLuint buffer, GLsizei size, const void *data, GLenum usage)
|
|
{
|
|
// only difference to EXT function is size parameter, so just upcast
|
|
glNamedBufferDataEXT(buffer, (GLsizeiptr)size, data, usage);
|
|
}
|
|
|
|
void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
|
|
{
|
|
byte *dummy = NULL;
|
|
|
|
if(m_State >= WRITING && data == NULL)
|
|
{
|
|
dummy = new byte[size];
|
|
memset(dummy, 0xdd, size);
|
|
data = dummy;
|
|
}
|
|
|
|
m_Real.glBufferData(target, size, data, usage);
|
|
|
|
size_t idx = BufferIdx(target);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
// detect buffer orphaning and just update backing store
|
|
if(m_State == WRITING_IDLE && record->GetDataPtr() != NULL && size == record->Length && usage == record->usage)
|
|
{
|
|
if(data)
|
|
memcpy(record->GetDataPtr(), data, (size_t)size);
|
|
else
|
|
memset(record->GetDataPtr(), 0xbe, (size_t)size);
|
|
return;
|
|
}
|
|
|
|
GLuint buffer = record->Resource.name;
|
|
|
|
// if we're recreating the buffer, clear the record and add new chunks. Normally
|
|
// we would just mark this record as dirty and pick it up on the capture frame as initial
|
|
// data, but we don't support (if it's even possible) querying out size etc.
|
|
// we need to add only the chunks required - glGenBuffers, glBindBuffer to current target,
|
|
// and this buffer storage. All other chunks have no effect
|
|
if(m_State == WRITING_IDLE && (record->GetDataPtr() != NULL || (record->Length > 0 && size != record->Length)))
|
|
{
|
|
// we need to maintain chunk ordering, so fetch the first two chunk IDs.
|
|
// We should have at least two by this point - glGenBuffers and whatever gave the record
|
|
// a size before.
|
|
RDCASSERT(record->NumChunks() >= 2);
|
|
|
|
// remove all but the first two chunks
|
|
while(record->NumChunks() > 2)
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
int32_t id2 = record->GetLastChunkID();
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
int32_t id1 = record->GetLastChunkID();
|
|
{
|
|
Chunk *c = record->GetLastChunk();
|
|
SAFE_DELETE(c);
|
|
record->PopChunk();
|
|
}
|
|
|
|
RDCASSERT(!record->HasChunks());
|
|
|
|
// add glGenBuffers chunk
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(GEN_BUFFER);
|
|
Serialise_glGenBuffers(1, &buffer);
|
|
|
|
record->AddChunk(scope.Get(), id1);
|
|
}
|
|
|
|
// add glBindBuffer chunk
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(record->datatype, buffer);
|
|
|
|
record->AddChunk(scope.Get(), id2);
|
|
}
|
|
|
|
// we're about to add the buffer data chunk
|
|
}
|
|
|
|
SCOPED_SERIALISE_CONTEXT(BUFFERDATA);
|
|
Serialise_glNamedBufferDataEXT(buffer, size, data, usage);
|
|
|
|
// for satisfying GL_MIN_MAP_BUFFER_ALIGNMENT
|
|
scope.SetAlignment(64);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
// if we've already created this is a renaming/data updating call. It should go in
|
|
// the frame record so we can 'update' the buffer as it goes in the frame.
|
|
// if we haven't created the buffer at all, it could be a mid-frame create and we
|
|
// should place it in the resource record, to happen before the frame.
|
|
if(m_State == WRITING_CAPFRAME && record->GetDataPtr())
|
|
{
|
|
// we could perhaps substitute this for a 'fake' glBufferSubData chunk?
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
record->AddChunk(chunk);
|
|
record->SetDataPtr(chunk->GetData());
|
|
record->Length = (int32_t)size;
|
|
record->usage = usage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Internal buffers should be allocated via dsa interfaces");
|
|
}
|
|
|
|
SAFE_DELETE_ARRAY(dummy);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glNamedBufferSubDataEXT(GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)));
|
|
SERIALISE_ELEMENT(uint64_t, Offset, (uint64_t)offset);
|
|
SERIALISE_ELEMENT(uint64_t, Bytesize, (uint64_t)size);
|
|
SERIALISE_ELEMENT_BUF(byte *, bytes, data, (size_t)Bytesize);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glNamedBufferSubDataEXT(res.name, (GLintptr)Offset, (GLsizeiptr)Bytesize, bytes);
|
|
|
|
SAFE_DELETE_ARRAY(bytes);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferSubDataEXT(GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data)
|
|
{
|
|
m_Real.glNamedBufferSubDataEXT(buffer, offset, size, data);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
RDCASSERT(record);
|
|
|
|
if(m_HighTrafficResources.find(record->GetResourceID()) != m_HighTrafficResources.end() && m_State != WRITING_CAPFRAME)
|
|
return;
|
|
|
|
SCOPED_SERIALISE_CONTEXT(BUFFERSUBDATA);
|
|
Serialise_glNamedBufferSubDataEXT(buffer, offset, size, data);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
record->AddChunk(chunk);
|
|
record->UpdateCount++;
|
|
|
|
if(record->UpdateCount > 10)
|
|
{
|
|
m_HighTrafficResources.insert(record->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizei size, const void *data)
|
|
{
|
|
// only difference to EXT function is size parameter, so just upcast
|
|
glNamedBufferSubDataEXT(buffer, offset, (GLsizeiptr)size, data);
|
|
}
|
|
|
|
void WrappedOpenGL::glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data)
|
|
{
|
|
m_Real.glBufferSubData(target, offset, size, data);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
GLResource res = record->Resource;
|
|
|
|
if(m_HighTrafficResources.find(record->GetResourceID()) != m_HighTrafficResources.end() && m_State != WRITING_CAPFRAME)
|
|
return;
|
|
|
|
SCOPED_SERIALISE_CONTEXT(BUFFERSUBDATA);
|
|
Serialise_glNamedBufferSubDataEXT(res.name, offset, size, data);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
m_ContextRecord->AddChunk(chunk);
|
|
else
|
|
{
|
|
record->AddChunk(chunk);
|
|
record->UpdateCount++;
|
|
|
|
if(record->UpdateCount > 10)
|
|
{
|
|
m_HighTrafficResources.insert(record->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glNamedCopyBufferSubDataEXT(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, readid, GetResourceManager()->GetID(BufferRes(GetCtx(), readBuffer)));
|
|
SERIALISE_ELEMENT(ResourceId, writeid, GetResourceManager()->GetID(BufferRes(GetCtx(), writeBuffer)));
|
|
SERIALISE_ELEMENT(uint64_t, ReadOffset, (uint64_t)readOffset);
|
|
SERIALISE_ELEMENT(uint64_t, WriteOffset, (uint64_t)writeOffset);
|
|
SERIALISE_ELEMENT(uint64_t, Bytesize, (uint64_t)size);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
GLResource readres = GetResourceManager()->GetLiveResource(readid);
|
|
GLResource writeres = GetResourceManager()->GetLiveResource(writeid);
|
|
m_Real.glNamedCopyBufferSubDataEXT(readres.name, writeres.name, (GLintptr)ReadOffset, (GLintptr)WriteOffset, (GLsizeiptr)Bytesize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glNamedCopyBufferSubDataEXT(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
|
|
{
|
|
CoherentMapImplicitBarrier();
|
|
|
|
m_Real.glNamedCopyBufferSubDataEXT(readBuffer, writeBuffer, readOffset, writeOffset, size);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *readrecord = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), readBuffer));
|
|
GLResourceRecord *writerecord = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), writeBuffer));
|
|
RDCASSERT(readrecord && writerecord);
|
|
|
|
SCOPED_SERIALISE_CONTEXT(COPYBUFFERSUBDATA);
|
|
Serialise_glNamedCopyBufferSubDataEXT(readBuffer, writeBuffer, readOffset, writeOffset, size);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
writerecord->AddChunk(chunk);
|
|
writerecord->AddParent(readrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glCopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizei size)
|
|
{
|
|
glNamedCopyBufferSubDataEXT(readBuffer, writeBuffer, readOffset, writeOffset, (GLsizeiptr)size);
|
|
}
|
|
|
|
void WrappedOpenGL::glCopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
|
|
{
|
|
CoherentMapImplicitBarrier();
|
|
|
|
m_Real.glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *readrecord = GetCtxData().m_BufferRecord[BufferIdx(readTarget)];
|
|
GLResourceRecord *writerecord = GetCtxData().m_BufferRecord[BufferIdx(writeTarget)];
|
|
RDCASSERT(readrecord && writerecord);
|
|
|
|
if(m_HighTrafficResources.find(writerecord->GetResourceID()) != m_HighTrafficResources.end() && m_State != WRITING_CAPFRAME)
|
|
return;
|
|
|
|
if(GetResourceManager()->IsResourceDirty(readrecord->GetResourceID()) && m_State != WRITING_CAPFRAME)
|
|
{
|
|
m_HighTrafficResources.insert(writerecord->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(writerecord->GetResourceID());
|
|
return;
|
|
}
|
|
|
|
SCOPED_SERIALISE_CONTEXT(COPYBUFFERSUBDATA);
|
|
Serialise_glNamedCopyBufferSubDataEXT(readrecord->Resource.name,
|
|
writerecord->Resource.name,
|
|
readOffset, writeOffset, size);
|
|
|
|
Chunk *chunk = scope.Get();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
m_ContextRecord->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
writerecord->AddChunk(chunk);
|
|
writerecord->AddParent(readrecord);
|
|
writerecord->UpdateCount++;
|
|
|
|
if(writerecord->UpdateCount > 60)
|
|
{
|
|
m_HighTrafficResources.insert(writerecord->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(writerecord->GetResourceID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindBufferBase(GLenum target, GLuint index, GLuint buffer)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(ResourceId, id, (buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId()));
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
if(id == ResourceId())
|
|
{
|
|
m_Real.glBindBuffer(Target, 0);
|
|
}
|
|
else
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glBindBufferBase(Target, Index, res.name);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindBufferBase(GLenum target, GLuint index, GLuint buffer)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
size_t idx = BufferIdx(target);
|
|
|
|
GLResourceRecord *r = NULL;
|
|
|
|
if(buffer == 0)
|
|
r = cd.m_BufferRecord[idx] = NULL;
|
|
else
|
|
r = cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
if(buffer && m_State == WRITING_CAPFRAME)
|
|
{
|
|
FrameRefType refType = eFrameRef_Read;
|
|
|
|
// these targets write to the buffer
|
|
if(target == eGL_ATOMIC_COUNTER_BUFFER ||
|
|
target == eGL_COPY_WRITE_BUFFER ||
|
|
target == eGL_PIXEL_PACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_TRANSFORM_FEEDBACK_BUFFER)
|
|
refType = eFrameRef_Write;
|
|
|
|
GetResourceManager()->MarkResourceFrameReferenced(cd.m_BufferRecord[idx]->GetResourceID(), refType);
|
|
}
|
|
|
|
// it's legal to re-type buffers, generate another BindBuffer chunk to rename
|
|
if(r && r->datatype != target)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffer);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
r->AddChunk(chunk);
|
|
}
|
|
|
|
// store as transform feedback record state
|
|
if(m_State == WRITING_IDLE && target == eGL_TRANSFORM_FEEDBACK_BUFFER && RecordUpdateCheck(cd.m_FeedbackRecord))
|
|
{
|
|
GLuint feedback = cd.m_FeedbackRecord->Resource.name;
|
|
|
|
// use glTransformFeedbackBufferBase to ensure the feedback object is bound when we bind the
|
|
// buffer
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_BASE);
|
|
Serialise_glTransformFeedbackBufferBase(feedback, index, buffer);
|
|
|
|
cd.m_FeedbackRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
// immediately consider buffers bound to transform feedbacks/SSBOs/atomic counters as dirty
|
|
if(m_State == WRITING_IDLE &&
|
|
(target == eGL_TRANSFORM_FEEDBACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_ATOMIC_COUNTER_BUFFER)
|
|
)
|
|
{
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffer));
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER_BASE);
|
|
Serialise_glBindBufferBase(target, index, buffer);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
m_Real.glBindBufferBase(target, index, buffer);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(ResourceId, id, (buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId()));
|
|
SERIALISE_ELEMENT(uint64_t, Offset, (uint64_t)offset);
|
|
SERIALISE_ELEMENT(uint64_t, Size, (uint64_t)size);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
if(id == ResourceId())
|
|
{
|
|
m_Real.glBindBuffer(Target, 0);
|
|
}
|
|
else
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glBindBufferRange(Target, Index, res.name, (GLintptr)Offset, (GLsizeiptr)Size);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
size_t idx = BufferIdx(target);
|
|
|
|
GLResourceRecord *r = NULL;
|
|
|
|
if(buffer == 0)
|
|
r = cd.m_BufferRecord[idx] = NULL;
|
|
else
|
|
r = cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
if(buffer && m_State == WRITING_CAPFRAME)
|
|
{
|
|
FrameRefType refType = eFrameRef_Read;
|
|
|
|
// these targets write to the buffer
|
|
if(target == eGL_ATOMIC_COUNTER_BUFFER ||
|
|
target == eGL_COPY_WRITE_BUFFER ||
|
|
target == eGL_PIXEL_PACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_TRANSFORM_FEEDBACK_BUFFER)
|
|
refType = eFrameRef_Write;
|
|
|
|
GetResourceManager()->MarkResourceFrameReferenced(cd.m_BufferRecord[idx]->GetResourceID(), refType);
|
|
}
|
|
|
|
// it's legal to re-type buffers, generate another BindBuffer chunk to rename
|
|
if(r && r->datatype != target)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffer);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
r->AddChunk(chunk);
|
|
}
|
|
|
|
// store as transform feedback record state
|
|
if(m_State == WRITING_IDLE && target == eGL_TRANSFORM_FEEDBACK_BUFFER && RecordUpdateCheck(cd.m_FeedbackRecord))
|
|
{
|
|
GLuint feedback = cd.m_FeedbackRecord->Resource.name;
|
|
|
|
// use glTransformFeedbackBufferRange to ensure the feedback object is bound when we bind the
|
|
// buffer
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_RANGE);
|
|
Serialise_glTransformFeedbackBufferRange(feedback, index, buffer, offset, (GLsizei)size);
|
|
|
|
cd.m_FeedbackRecord->AddChunk(scope.Get());
|
|
}
|
|
|
|
// immediately consider buffers bound to transform feedbacks/SSBOs/atomic counters as dirty
|
|
if(m_State == WRITING_IDLE &&
|
|
(target == eGL_TRANSFORM_FEEDBACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_ATOMIC_COUNTER_BUFFER)
|
|
)
|
|
{
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffer));
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER_RANGE);
|
|
Serialise_glBindBufferRange(target, index, buffer, offset, size);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
m_Real.glBindBufferRange(target, index, buffer, offset, size);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindBuffersBase(GLenum target, GLuint first, GLsizei count, const GLuint *buffers)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(uint32_t, First, first);
|
|
SERIALISE_ELEMENT(int32_t, Count, count);
|
|
|
|
GLuint *bufs = NULL;
|
|
if(m_State <= EXECUTING) bufs = new GLuint[Count];
|
|
|
|
for(int32_t i=0; i < Count; i++)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffers[i])));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(id != ResourceId())
|
|
bufs[i] = GetResourceManager()->GetLiveResource(id).name;
|
|
else
|
|
bufs[i] = 0;
|
|
}
|
|
}
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glBindBuffersBase(Target, First, Count, bufs);
|
|
|
|
delete[] bufs;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindBuffersBase(GLenum target, GLuint first, GLsizei count, const GLuint *buffers)
|
|
{
|
|
m_Real.glBindBuffersBase(target, first, count, buffers);
|
|
|
|
ContextData &cd = GetCtxData();
|
|
|
|
if(m_State >= WRITING && buffers && count > 0)
|
|
{
|
|
size_t idx = BufferIdx(target);
|
|
|
|
if(buffers[0] == 0)
|
|
cd.m_BufferRecord[idx] = NULL;
|
|
else
|
|
cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[0]));
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
FrameRefType refType = eFrameRef_Read;
|
|
|
|
// these targets write to the buffer
|
|
if(target == eGL_ATOMIC_COUNTER_BUFFER ||
|
|
target == eGL_COPY_WRITE_BUFFER ||
|
|
target == eGL_PIXEL_PACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_TRANSFORM_FEEDBACK_BUFFER)
|
|
refType = eFrameRef_Write;
|
|
|
|
for(GLsizei i=0; i < count; i++)
|
|
if(buffers[i])
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResourceManager()->GetID(BufferRes(GetCtx(), buffers[i])), refType);
|
|
}
|
|
|
|
for(int i=0; i < count; i++)
|
|
{
|
|
GLResourceRecord *r = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[i]));
|
|
|
|
// it's legal to re-type buffers, generate another BindBuffer chunk to rename
|
|
if(r->datatype != target)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffers[i]);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
r->AddChunk(chunk);
|
|
}
|
|
}
|
|
|
|
// store as transform feedback record state
|
|
if(m_State == WRITING_IDLE && target == eGL_TRANSFORM_FEEDBACK_BUFFER && RecordUpdateCheck(cd.m_FeedbackRecord))
|
|
{
|
|
GLuint feedback = cd.m_FeedbackRecord->Resource.name;
|
|
|
|
for(int i=0; i < count; i++)
|
|
{
|
|
// use glTransformFeedbackBufferBase to ensure the feedback object is bound when we bind the
|
|
// buffer
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_BASE);
|
|
Serialise_glTransformFeedbackBufferBase(feedback, first+i, buffers[i]);
|
|
|
|
cd.m_FeedbackRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
// immediately consider buffers bound to transform feedbacks/SSBOs/atomic counters as dirty
|
|
if(m_State == WRITING_IDLE &&
|
|
(target == eGL_TRANSFORM_FEEDBACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_ATOMIC_COUNTER_BUFFER)
|
|
)
|
|
{
|
|
for(int i=0; i < count; i++)
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffers[i]));
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFERS_BASE);
|
|
Serialise_glBindBuffersBase(target, first, count, buffers);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindBuffersRange(GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(uint32_t, First, first);
|
|
SERIALISE_ELEMENT(int32_t, Count, count);
|
|
|
|
GLuint *bufs = NULL;
|
|
GLintptr *offs = NULL;
|
|
GLsizeiptr *sz = NULL;
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
bufs = new GLuint[Count];
|
|
offs = new GLintptr[Count];
|
|
sz = new GLsizeiptr[Count];
|
|
}
|
|
|
|
for(int32_t i=0; i < Count; i++)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffers[i])));
|
|
SERIALISE_ELEMENT(uint64_t, offset, (uint64_t)offsets[i]);
|
|
SERIALISE_ELEMENT(uint64_t, size, (uint64_t)sizes[i]);
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(id != ResourceId())
|
|
bufs[i] = GetResourceManager()->GetLiveResource(id).name;
|
|
else
|
|
bufs[i] = 0;
|
|
offs[i] = (GLintptr)offset;
|
|
sz[i] = (GLsizeiptr)sizes;
|
|
}
|
|
}
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glBindBuffersRange(Target, First, Count, bufs, offs, sz);
|
|
|
|
delete[] bufs;
|
|
delete[] offs;
|
|
delete[] sz;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindBuffersRange(GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes)
|
|
{
|
|
m_Real.glBindBuffersRange(target, first, count, buffers, offsets, sizes);
|
|
|
|
ContextData &cd = GetCtxData();
|
|
|
|
if(m_State >= WRITING && buffers && count > 0)
|
|
{
|
|
size_t idx = BufferIdx(target);
|
|
|
|
if(buffers[0] == 0)
|
|
cd.m_BufferRecord[idx] = NULL;
|
|
else
|
|
cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[0]));
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
FrameRefType refType = eFrameRef_Read;
|
|
|
|
// these targets write to the buffer
|
|
if(target == eGL_ATOMIC_COUNTER_BUFFER ||
|
|
target == eGL_COPY_WRITE_BUFFER ||
|
|
target == eGL_PIXEL_PACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_TRANSFORM_FEEDBACK_BUFFER)
|
|
refType = eFrameRef_Write;
|
|
|
|
for(GLsizei i=0; i < count; i++)
|
|
if(buffers[i])
|
|
GetResourceManager()->MarkResourceFrameReferenced(GetResourceManager()->GetID(BufferRes(GetCtx(), buffers[i])), refType);
|
|
}
|
|
|
|
for(int i=0; i < count; i++)
|
|
{
|
|
GLResourceRecord *r = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[i]));
|
|
|
|
// it's legal to re-type buffers, generate another BindBuffer chunk to rename
|
|
if(r->datatype != target)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFER);
|
|
Serialise_glBindBuffer(target, buffers[i]);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
r->AddChunk(chunk);
|
|
}
|
|
}
|
|
|
|
// store as transform feedback record state
|
|
if(m_State == WRITING_IDLE && target == eGL_TRANSFORM_FEEDBACK_BUFFER && RecordUpdateCheck(cd.m_FeedbackRecord))
|
|
{
|
|
GLuint feedback = cd.m_FeedbackRecord->Resource.name;
|
|
|
|
for(int i=0; i < count; i++)
|
|
{
|
|
// use glTransformFeedbackBufferRange to ensure the feedback object is bound when we bind the
|
|
// buffer
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_RANGE);
|
|
Serialise_glTransformFeedbackBufferRange(feedback, first+i, buffers[i], offsets[i], (GLsizei)sizes[i]);
|
|
|
|
cd.m_FeedbackRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
// immediately consider buffers bound to transform feedbacks/SSBOs/atomic counters as dirty
|
|
if(m_State == WRITING_IDLE &&
|
|
(target == eGL_TRANSFORM_FEEDBACK_BUFFER ||
|
|
target == eGL_SHADER_STORAGE_BUFFER ||
|
|
target == eGL_ATOMIC_COUNTER_BUFFER)
|
|
)
|
|
{
|
|
for(int i=0; i < count; i++)
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffers[i]));
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_BUFFERS_RANGE);
|
|
Serialise_glBindBuffersRange(target, first, count, buffers, offsets, sizes);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glInvalidateBufferData(GLuint buffer)
|
|
{
|
|
m_Real.glInvalidateBufferData(buffer);
|
|
|
|
if(m_State == WRITING_IDLE)
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffer));
|
|
}
|
|
|
|
void WrappedOpenGL::glInvalidateBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr length)
|
|
{
|
|
m_Real.glInvalidateBufferSubData(buffer, offset, length);
|
|
|
|
if(m_State == WRITING_IDLE)
|
|
GetResourceManager()->MarkDirtyResource(BufferRes(GetCtx(), buffer));
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Mapping
|
|
|
|
/************************************************************************
|
|
*
|
|
* Mapping tends to be the most complex/dense bit of the capturing process, as there are a lot of
|
|
* carefully considered use cases and edge cases to be aware of.
|
|
*
|
|
* The primary motivation is, obviously, correctness - where we have to sacrifice performance,
|
|
* clarity for correctness, we do. Second to that, we try and keep things simple/clear where the
|
|
* performance sacrifice will be minimal, and generally we try to remove overhead entirely for
|
|
* high-traffic maps, such that we only step in where necessary.
|
|
*
|
|
* We'll consider "normal" maps of buffers, and persistent maps, separately. Note that in all cases
|
|
* we can guarantee that the buffer being mapped has correctly-sized backing store available,
|
|
* created in the glBufferData or glBufferStorage call. We also only need to consider the case of
|
|
* glMapNamedBufferRangeEXT, glUnmapNamedBufferEXT and glFlushMappedNamedBufferRange - all other
|
|
* entry points are mapped to one of these in a fairly simple fashion.
|
|
*
|
|
*
|
|
* glMapNamedBufferRangeEXT:
|
|
*
|
|
* For a normal map, we decide to either record/intercept it, or to step out of the way and allow
|
|
* the application to map directly to the GL buffer. We can only map directly when idle capturing,
|
|
* when capturing a frame we must capture all maps to be correct. Generally we perform a direct map
|
|
* either if this resource is being mapped often and we want to remove overhead, or if the map
|
|
* interception would be more complex than it's worth.
|
|
*
|
|
* The first checks are to see if we've already "given up" on a buffer, in which case we map
|
|
* directly again.
|
|
*
|
|
* Next, if the map is for write and the buffer is not invalidated, we also map directly.
|
|
* [NB: Since our buffer contents should be perfect at this point, we may not need to worry about
|
|
* non-invalidating maps. Potential future improvement.]
|
|
*
|
|
* At this point, if the map is to be done directly, we pass the parameters onto GL and return
|
|
* the result, marking the map with status GLResourceRecord::Mapped_Ignore_Real. Note that this
|
|
* means we have no idea what happens with the map, and the buffer contents after that are to us
|
|
* undefined.
|
|
*
|
|
* If not, we will be intercepting the map. If it's read-only this is relatively simple to satisfy,
|
|
* as we just need to fetch the current buffer contents and return the appropriately offsetted
|
|
* pointer. [NB: Again our buffer contents should still be perfect here, this fetch may be
|
|
* redundant.] The map status is recorded as GLResourceRecord::Mapped_Read
|
|
*
|
|
* At this point we are intercepting a map for write, and it depends on whether or not we are
|
|
* capturing a frame or just idle.
|
|
*
|
|
* If idle the handling is relatively simple, we just offset the pointer and return, marking the
|
|
* map as GLResourceRecord::Mapped_Write. Note that here we also increment a counter, and if this
|
|
* counter reaches a high enough number (arbitrary limit), we mark the buffer as high-traffic so
|
|
* that we'll stop intercepting maps and reduce overhead on this buffer.
|
|
*
|
|
* If frame capturing it is more complex. The backing store of the buffer must be preserved as it
|
|
* will contain the contents at the start of the frame. Instead we allocate two shadow storage
|
|
* copies on first use. Shadow storage [1] contains the 'current' contents of the buffer -
|
|
* when first allocated, if the map is non-invalidating, it will be filled with the buffer contents
|
|
* at that point. If the map is invalidating, it will be reset to 0xcc to help find bugs caused by
|
|
* leaving valid data behind in invalidated buffer memory.
|
|
*
|
|
* Shadow buffer [0] is the buffer that is returned to the user code. Every time it is updated
|
|
* with the contents of [1]. This way both buffers are always identical and contain the latest
|
|
* buffer contents. These buffers are used later in unmap, but Map() will return the appropriately
|
|
* offsetted pointer, and mark the map as GLResourceRecord::Mapped_Write.
|
|
*
|
|
*
|
|
* glUnmapNamedBufferEXT:
|
|
*
|
|
* The unmap becomes an actual chunk for serialisation when necessary, so we'll discuss the handling
|
|
* of the unmap call, and then how it is serialised.
|
|
*
|
|
* Unmap's handling varies depending on the status of the map, as set above in glMapNamedBufferRangeEXT.
|
|
*
|
|
* GLResourceRecord::Unmapped is an error case, indicating we haven't had a corresponding Map() call.
|
|
*
|
|
* GLResourceRecord::Mapped_Read is a no-op as we can just discard it, the pointer we returned from Map()
|
|
* was into our backing store.
|
|
*
|
|
* GLResourceRecord::Mapped_Ignore_Real is likewise a no-op as the GL pointer was updated directly by
|
|
* user code, we weren't involved. However if we are now capturing a frame, it indicates a Map() was
|
|
* made before this frame began, so this frame cannot be captured - we will need to try again next
|
|
* frame, where a map will not be allowed to go into GLResourceRecord::Mapped_Ignore_Real.
|
|
*
|
|
* GLResourceRecord::Mapped_Write is the only case that will generate a serialised unmap chunk. If we
|
|
* are idle, then all we need to do is map the 'real' GL buffer, copy across our backing store, and
|
|
* unmap. We only map the range that was modified. Then everything is complete as the user code updated
|
|
* our backing store. If we are capturing a frame, then we go into the serialise function and serialise
|
|
* out a chunk.
|
|
*
|
|
* Finally we set the map status back to GLResourceRecord::Unmapped.
|
|
*
|
|
* When serialising out a map, we serialise the details of the map (which buffer, offset, length) and
|
|
* then for non-invalidating maps of >512 byte buffers we perform a difference compare between the two
|
|
* shadow storage buffers that were set up in glMapNamedBufferRangeEXT. We then serialise out a buffer
|
|
* of the difference segment, and on replay we map and update this segment of the buffer.
|
|
*
|
|
* The reason for finding the actual difference segment is that many maps will be of a large region
|
|
* or even the whole buffer, but only update a small section, perhaps once per drawcall. So serialising
|
|
* the entirety of a large buffer many many times can rapidly inflate the size of the log. The savings
|
|
* from this can be many GBs as if a 4MB buffer is updated 1000 times, each time only updating 1KB,
|
|
* this is a difference between 1MB and 4000MB in written data, most of which is redundant in the last
|
|
* case.
|
|
*
|
|
*
|
|
* glFlushMappedNamedBufferRangeEXT:
|
|
*
|
|
* Now consider the specialisation of the above, for maps that have GL_MAP_FLUSH_EXPLICIT_BIT enabled.
|
|
*
|
|
* For the most part, these maps can be treated very similarly to normal maps, however in the case of
|
|
* unmapping we will skip creating an unmap chunk and instead just allow the unmap to be discarded.
|
|
* Instead we will serialise out a chunk for each glFlushMappedNamedBufferRangeEXT call. We will also
|
|
* include flush explicit maps along with the others that we choose to map directly when possible - so
|
|
* if we're capturing idle a flush explicit map will go straight to GL and be handled as with
|
|
* GLResourceRecord::Mapped_Ignore_Real above.
|
|
*
|
|
* For this reason, if a map status is GLResourceRecord::Mapped_Ignore_Real then we simply pass the
|
|
* flush range along to real GL. Again if we are capturing a frame now, this map has been 'missed' and
|
|
* we must try again next frame to capture. Likewise as with Unmap GLResourceRecord::Unmapped is an
|
|
* error, and for flushing we do not need to consider GLResourceRecord::Mapped_Read (it doesn't make
|
|
* sense for this case).
|
|
*
|
|
* So we only serialise out a flush chunk if we are capturing a frame, and the map is correctly
|
|
* GLResourceRecord::Mapped_Write. We clamp the flushed range to the size of the map (in case the user
|
|
* code didn't do this). Unlike map we do not perform any difference compares, but rely on the user to
|
|
* only flush the minimal range, and serialise the entire range out as a buffer. We also update the
|
|
* shadow storage buffers so that if the buffer is subsequently mapped without flush explicit, we have
|
|
* the 'current' contents to perform accurate compares with.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* Persistant maps:
|
|
*
|
|
* The above process handles "normal" maps that happen between other GL commands that use the buffer
|
|
* contents. Maps that are persistent need to be handled carefully since there are other knock-ons for
|
|
* correctness and proper tracking. They come in two major forms - coherent and non-coherent.
|
|
*
|
|
* Non-coherent maps are the 'easy' case, and in all cases should be recommended whenever users do
|
|
* persistent mapping. Indeed because of the implementation details, coherent maps may come at a
|
|
* performance penalty even when RenderDoc is not used and it is simply the user code using GL directly.
|
|
*
|
|
* The important thing is that persistent maps *must always* be intercepted regardless of circumstance,
|
|
* as in theory they may never be mapped again. We get hints to help us with these maps, as the buffers
|
|
* must have been created with glBufferStorage and must have the matching persistent and optionally
|
|
* coherent bits set in the flags bitfield.
|
|
*
|
|
* Note also that non-coherent maps tend to go hand in hand with flush explicit maps (although this is
|
|
* not guaranteed, it is highly likely).
|
|
*
|
|
* Non-coherent mappable buffers are GL-mapped on creation, and remain GL-mapped until their destruction
|
|
* regardless of what user code does. We keep this 'real' GL-mapped buffer around permanently but it is
|
|
* never returned to user code. Instead we handle maps otherwise as above (taking care to always
|
|
* intercept), and return the user a pointer to our backing store. Then every time a map flush happens
|
|
* instead of temporarily mapping and unmapping the GL buffer, we copy into the appropriate place in our
|
|
* persistent map pointer. If an unmap happens and the map wasn't flush-explicit, we copy the mapped
|
|
* region then. In this way we maintain correctness - the copies are "delayed" by the time between
|
|
* user code writing into our memory, and us copying into the real memory. However this is valid as it
|
|
* happens synchronously with a flush, unmap or other event and by definition non-coherent maps aren't
|
|
* visible to the GPU until after those operations.
|
|
*
|
|
* There is also the function glMemoryBarrier with bit GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT. This has the
|
|
* effect of acting as if all currently persistent-mapped regions were simultaneously flushed. This is
|
|
* exactly how we implement it - we store a list of all current user persistent maps and any time this
|
|
* bit is passed to glMemoryBarrier, we manually call into glFlushMappedNamedBufferRangeEXT() with the
|
|
* appropriate parameters and handling is otherwise identical.
|
|
*
|
|
* The final piece of the puzzle is coherent mapped buffers. Since we must break the coherency carefully
|
|
* (see below), we map coherent buffers as non-coherent at creation time, the same as above.
|
|
*
|
|
* To satisfy the demands of being coherent, we need to transparently propogate any changes between the
|
|
* user written data and the 'real' memory, without any call to intercept - there would be no need to
|
|
* call glMemoryBarrier or glFlushMappedNamedBufferRangeEXT. To do this, we have shadow storage
|
|
* allocated as in the "normal" mapping path all the time, and we insert a manual call to essentially
|
|
* the same code as glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT) in every intercepted function
|
|
* call that could depend on the results of the buffer. We then check if any write/change has happened
|
|
* by comparing to the shadow storage, and if so we perform a manual flush of that changed region and
|
|
* update the shadow storage for next time.
|
|
*
|
|
* This "fake coherency" is the reason we can map the buffer as non-coherent, since we will be performing
|
|
* copies and flushes manually to emulate the coherency to allow our interception in the middle.
|
|
*
|
|
* By definition, there will be *many* of these places where the buffer results could be used, not least
|
|
* any buffer copy, any texture copy (since a texture buffer could be created), any draw or dispatch,
|
|
* etc. At each of these points there will be a cost for each coherent map of checking for changes and it
|
|
* will scale with the size of the buffers. This is a large performance penalty but one that can't be
|
|
* easily avoided. This is another reason why coherent maps should be avoided.
|
|
*
|
|
* Note that this also involves a behaviour change that affects correctness - a user write to memory is
|
|
* not visible as soon as the write happens, but only on the next api point where the write could have
|
|
* an effect. In correct code this should not be a problem as relying on any other behaviour would be
|
|
* impossible - if you wrote into memory expecting commands in flight to be affected you could not ensure
|
|
* correct ordering. However, obvious from that description, this is precisely a race condition bug if
|
|
* user code did do that - which means race condition bugs will be hidden by the nature of this tracing.
|
|
* This is unavoidable without the extreme performance hit of making all coherent maps read-write, and
|
|
* performing a read-back at every sync point to find every change. Which by itself may also hide race
|
|
* conditions anyway.
|
|
*
|
|
*
|
|
* Implementation notes:
|
|
*
|
|
* The record->Map.ptr is the *offsetted* pointer, ie. a pointer to the beginning of the mapped region,
|
|
* at record->Map.offset bytes from the start of the buffer.
|
|
*
|
|
* record->Map.persistentPtr points to the *base* of the buffer, not offsetted by any current map.
|
|
*
|
|
* Likewise the shadow storage pointers point to the base of a buffer-sized allocation each.
|
|
*
|
|
************************************************************************/
|
|
|
|
void *WrappedOpenGL::glMapNamedBufferRangeEXT(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access)
|
|
{
|
|
// see above for high-level explanation of how mapping is handled
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
bool directMap = false;
|
|
|
|
// first check if we've already given up on these buffers
|
|
if(m_State != WRITING_CAPFRAME && m_HighTrafficResources.find(record->GetResourceID()) != m_HighTrafficResources.end())
|
|
directMap = true;
|
|
|
|
if(!directMap && m_State != WRITING_CAPFRAME && GetResourceManager()->IsResourceDirty(record->GetResourceID()))
|
|
directMap = true;
|
|
|
|
bool invalidateMap = (access & (GL_MAP_INVALIDATE_BUFFER_BIT|GL_MAP_INVALIDATE_RANGE_BIT)) != 0;
|
|
bool flushExplicitMap = (access & GL_MAP_FLUSH_EXPLICIT_BIT) != 0;
|
|
|
|
// if this map is writing and doesn't invalidate, or is flush explicit, map directly
|
|
if(!directMap && (!invalidateMap || flushExplicitMap) && (access & GL_MAP_WRITE_BIT) && m_State != WRITING_CAPFRAME)
|
|
directMap = true;
|
|
|
|
// persistent maps must ALWAYS be intercepted
|
|
if(access & GL_MAP_PERSISTENT_BIT)
|
|
directMap = false;
|
|
|
|
if(directMap)
|
|
{
|
|
m_HighTrafficResources.insert(record->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
|
|
record->Map.offset = offset;
|
|
record->Map.length = length;
|
|
record->Map.access = access;
|
|
record->Map.invalidate = invalidateMap;
|
|
|
|
// store a list of all persistent maps, and subset of all coherent maps
|
|
if(access & GL_MAP_PERSISTENT_BIT)
|
|
{
|
|
Atomic::Inc64(&record->Map.persistentMaps);
|
|
m_PersistentMaps.insert(record);
|
|
if(record->Map.access & GL_MAP_COHERENT_BIT)
|
|
m_CoherentMaps.insert(record);
|
|
}
|
|
|
|
// if we're doing a direct map, pass onto GL and return
|
|
if(directMap)
|
|
{
|
|
record->Map.ptr = (byte *)m_Real.glMapNamedBufferRangeEXT(buffer, offset, length, access);
|
|
record->Map.status = GLResourceRecord::Mapped_Ignore_Real;
|
|
|
|
return record->Map.ptr;
|
|
}
|
|
|
|
// only squirrel away read-only maps, read-write can just be treated as write-only
|
|
if((access & (GL_MAP_READ_BIT|GL_MAP_WRITE_BIT)) == GL_MAP_READ_BIT)
|
|
{
|
|
byte *ptr = record->GetDataPtr();
|
|
|
|
if(record->Map.persistentPtr)
|
|
ptr = record->GetShadowPtr(0);
|
|
|
|
RDCASSERT(ptr);
|
|
|
|
ptr += offset;
|
|
|
|
m_Real.glGetNamedBufferSubDataEXT(buffer, offset, length, ptr);
|
|
|
|
record->Map.ptr = ptr;
|
|
record->Map.status = GLResourceRecord::Mapped_Read;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
// below here, handle write maps to the backing store
|
|
byte *ptr = record->GetDataPtr();
|
|
|
|
RDCASSERT(ptr);
|
|
{
|
|
// persistent maps get particular handling
|
|
if(access & GL_MAP_PERSISTENT_BIT)
|
|
{
|
|
// persistent pointers are always into the shadow storage, this way we can use the backing
|
|
// store for 'initial' buffer contents as with any other buffer. We also need to keep a
|
|
// comparison & modified buffer in case the application calls glMemoryBarrier(..) at any
|
|
// time.
|
|
|
|
// if we're invalidating, mark the whole range as 0xcc
|
|
if(invalidateMap)
|
|
{
|
|
memset(record->GetShadowPtr(0)+offset, 0xcc, length);
|
|
memset(record->GetShadowPtr(1)+offset, 0xcc, length);
|
|
}
|
|
|
|
record->Map.ptr = ptr = record->GetShadowPtr(0)+offset;
|
|
record->Map.status = GLResourceRecord::Mapped_Write;
|
|
}
|
|
else if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
byte *shadow = (byte *)record->GetShadowPtr(0);
|
|
|
|
// if we don't have a shadow pointer, need to allocate & initialise
|
|
if(shadow == NULL)
|
|
{
|
|
GLint buflength;
|
|
m_Real.glGetNamedBufferParameterivEXT(buffer, eGL_BUFFER_SIZE, &buflength);
|
|
|
|
// allocate our shadow storage
|
|
record->AllocShadowStorage(buflength, 64);
|
|
shadow = (byte *)record->GetShadowPtr(0);
|
|
|
|
// if we're not invalidating, we need the existing contents
|
|
if(!invalidateMap)
|
|
{
|
|
// need to fetch the whole buffer's contents, not just the mapped range,
|
|
// as next time we won't re-fetch and might need the rest of the contents
|
|
if(GetResourceManager()->IsResourceDirty(record->GetResourceID()))
|
|
{
|
|
// Perhaps we could get these contents from the frame initial state buffer?
|
|
|
|
m_Real.glGetNamedBufferSubDataEXT(buffer, 0, buflength, shadow);
|
|
}
|
|
else
|
|
{
|
|
memcpy(shadow, record->GetDataPtr(), buflength);
|
|
}
|
|
}
|
|
|
|
// copy into second shadow buffer ready for comparison later
|
|
memcpy(record->GetShadowPtr(1), shadow, buflength);
|
|
}
|
|
|
|
// if we're invalidating, mark the whole range as 0xcc
|
|
if(invalidateMap)
|
|
{
|
|
memset(shadow+offset, 0xcc, length);
|
|
memset(record->GetShadowPtr(1)+offset, 0xcc, length);
|
|
}
|
|
|
|
record->Map.ptr = ptr = shadow;
|
|
record->Map.status = GLResourceRecord::Mapped_Write;
|
|
}
|
|
else if(m_State == WRITING_IDLE)
|
|
{
|
|
// return buffer backing store pointer, offsetted
|
|
ptr += offset;
|
|
|
|
record->Map.ptr = ptr;
|
|
record->Map.status = GLResourceRecord::Mapped_Write;
|
|
|
|
record->UpdateCount++;
|
|
|
|
// mark as high-traffic if we update it often enough
|
|
if(record->UpdateCount > 60)
|
|
{
|
|
m_HighTrafficResources.insert(record->GetResourceID());
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
return m_Real.glMapNamedBufferRangeEXT(buffer, offset, length, access);
|
|
}
|
|
|
|
void *WrappedOpenGL::glMapNamedBufferRange(GLuint buffer, GLintptr offset, GLsizei length, GLbitfield access)
|
|
{
|
|
// only difference to EXT function is size parameter, so just upcast
|
|
return glMapNamedBufferRangeEXT(buffer, offset, (GLsizeiptr)length, access);
|
|
}
|
|
|
|
void *WrappedOpenGL::glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
if(record)
|
|
return glMapNamedBufferRangeEXT(record->Resource.name, offset, length, access);
|
|
|
|
RDCERR("glMapBufferRange: Couldn't get resource record for target %x - no buffer bound?", target);
|
|
}
|
|
|
|
return m_Real.glMapBufferRange(target, offset, length, access);
|
|
}
|
|
|
|
// the glMapBuffer functions are equivalent to glMapBufferRange - so we just pass through
|
|
void *WrappedOpenGL::glMapNamedBufferEXT(GLuint buffer, GLenum access)
|
|
{
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
RDCASSERT(record);
|
|
|
|
if(record)
|
|
{
|
|
GLbitfield accessBits = 0;
|
|
|
|
if(access == eGL_READ_ONLY) accessBits = eGL_MAP_READ_BIT;
|
|
else if(access == eGL_WRITE_ONLY) accessBits = eGL_MAP_WRITE_BIT;
|
|
else if(access == eGL_READ_WRITE) accessBits = eGL_MAP_READ_BIT|eGL_MAP_WRITE_BIT;
|
|
return glMapNamedBufferRangeEXT(record->Resource.name, 0, record->Length, accessBits);
|
|
}
|
|
|
|
RDCERR("glMapNamedBufferEXT: Couldn't get resource record for buffer %x!", buffer);
|
|
}
|
|
|
|
return m_Real.glMapNamedBufferEXT(buffer, access);
|
|
}
|
|
|
|
void *WrappedOpenGL::glMapBuffer(GLenum target, GLenum access)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
if(record)
|
|
{
|
|
GLbitfield accessBits = 0;
|
|
|
|
if(access == eGL_READ_ONLY) accessBits = eGL_MAP_READ_BIT;
|
|
else if(access == eGL_WRITE_ONLY) accessBits = eGL_MAP_WRITE_BIT;
|
|
else if(access == eGL_READ_WRITE) accessBits = eGL_MAP_READ_BIT|eGL_MAP_WRITE_BIT;
|
|
return glMapNamedBufferRangeEXT(record->Resource.name, 0, record->Length, accessBits);
|
|
}
|
|
|
|
RDCERR("glMapBuffer: Couldn't get resource record for target %x - no buffer bound?", target);
|
|
}
|
|
|
|
return m_Real.glMapBuffer(target, access);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glUnmapNamedBufferEXT(GLuint buffer)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
GLResourceRecord *record = NULL;
|
|
|
|
if(m_State >= WRITING)
|
|
record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
SERIALISE_ELEMENT(ResourceId, bufID, record->GetResourceID());
|
|
SERIALISE_ELEMENT(uint64_t, offs, record->Map.offset);
|
|
SERIALISE_ELEMENT(uint64_t, len, record->Map.length);
|
|
|
|
uint64_t bufBindStart = 0;
|
|
|
|
size_t diffStart = 0;
|
|
size_t diffEnd = (size_t)len;
|
|
|
|
if(m_State == WRITING_CAPFRAME &&
|
|
// don't bother checking diff range for tiny buffers
|
|
len > 512 &&
|
|
// if the map has a sub-range specified, trust the user to have specified
|
|
// a minimal range, similar to glFlushMappedBufferRange, so don't find diff
|
|
// range.
|
|
record->Map.offset == 0 && record->Map.length == record->Length &&
|
|
// similarly for invalidate maps, we want to update the whole buffer
|
|
!record->Map.invalidate)
|
|
{
|
|
bool found = FindDiffRange(record->Map.ptr, record->GetShadowPtr(1)+offs, (size_t)len, diffStart, diffEnd);
|
|
if(found)
|
|
{
|
|
static size_t saved = 0;
|
|
|
|
saved += (size_t)len - (diffEnd-diffStart);
|
|
|
|
RDCDEBUG("Mapped resource size %u, difference: %u -> %u. Total bytes saved so far: %u",
|
|
(uint32_t)len, (uint32_t)diffStart, (uint32_t)diffEnd, (uint32_t)saved);
|
|
|
|
len = diffEnd-diffStart;
|
|
}
|
|
else
|
|
{
|
|
diffStart = 0;
|
|
diffEnd = 0;
|
|
|
|
len = 1;
|
|
}
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME && record->GetShadowPtr(1))
|
|
{
|
|
memcpy(record->GetShadowPtr(1)+diffStart, record->Map.ptr+diffStart, diffEnd-diffStart);
|
|
}
|
|
|
|
if(m_State == WRITING_IDLE)
|
|
{
|
|
diffStart = 0;
|
|
diffEnd = (size_t)len;
|
|
}
|
|
|
|
SERIALISE_ELEMENT(uint32_t, DiffStart, (uint32_t)diffStart);
|
|
SERIALISE_ELEMENT(uint32_t, DiffEnd, (uint32_t)diffEnd);
|
|
|
|
SERIALISE_ELEMENT_BUF(byte *, data, record->Map.ptr+diffStart, (size_t)len);
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(bufID);
|
|
buffer = res.name;
|
|
}
|
|
|
|
if(DiffEnd > DiffStart)
|
|
{
|
|
if(record && record->Map.persistentPtr)
|
|
{
|
|
// if we have a persistent mapped pointer, copy the range into the 'real' memory and
|
|
// do a flush. Note the persistent pointer is always to the base of the buffer so we
|
|
// need to account for the offset
|
|
|
|
memcpy(record->Map.persistentPtr+offs+DiffStart, record->Map.ptr+DiffStart, DiffEnd-DiffStart);
|
|
m_Real.glFlushMappedNamedBufferRangeEXT(buffer, GLintptr(offs+DiffStart), DiffEnd-DiffStart);
|
|
}
|
|
else
|
|
{
|
|
void *ptr = m_Real.glMapNamedBufferRangeEXT(buffer, (GLintptr)(offs+DiffStart), GLsizeiptr(DiffEnd-DiffStart), GL_MAP_WRITE_BIT);
|
|
memcpy(ptr, data, size_t(DiffEnd-DiffStart));
|
|
m_Real.glUnmapNamedBufferEXT(buffer);
|
|
}
|
|
}
|
|
|
|
if(m_State < WRITING)
|
|
delete[] data;
|
|
|
|
return true;
|
|
}
|
|
|
|
GLboolean WrappedOpenGL::glUnmapNamedBufferEXT(GLuint buffer)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
auto status = record->Map.status;
|
|
|
|
GLboolean ret = GL_TRUE;
|
|
|
|
switch(status)
|
|
{
|
|
case GLResourceRecord::Unmapped:
|
|
RDCERR("Unmapped buffer being passed to glUnmapBuffer");
|
|
break;
|
|
case GLResourceRecord::Mapped_Read:
|
|
// can ignore
|
|
break;
|
|
case GLResourceRecord::Mapped_Ignore_Real:
|
|
if(m_State == WRITING_CAPFRAME)
|
|
RDCERR("Failed to cap frame - uncapped Map/Unmap");
|
|
// need to do the real unmap
|
|
ret = m_Real.glUnmapNamedBufferEXT(buffer);
|
|
break;
|
|
case GLResourceRecord::Mapped_Write:
|
|
{
|
|
if(record->Map.access & GL_MAP_FLUSH_EXPLICIT_BIT)
|
|
{
|
|
// do nothing, any flushes that happened were handled,
|
|
// and we won't do any other updates here or make a chunk.
|
|
}
|
|
else if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(UNMAP);
|
|
Serialise_glUnmapNamedBufferEXT(buffer);
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
else if(m_State == WRITING_IDLE)
|
|
{
|
|
if(record->Map.persistentPtr)
|
|
{
|
|
// if we have a persistent mapped pointer, copy the range into the 'real' memory and
|
|
// do a flush. Note the persistent pointer is always to the base of the buffer so we
|
|
// need to account for the offset
|
|
|
|
memcpy(record->Map.persistentPtr+record->Map.offset, record->Map.ptr, record->Map.length);
|
|
m_Real.glFlushMappedNamedBufferRangeEXT(buffer, record->Map.offset, record->Map.length);
|
|
|
|
// update shadow storage
|
|
memcpy(record->GetShadowPtr(1)+record->Map.offset, record->Map.ptr, record->Map.length);
|
|
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
else
|
|
{
|
|
// if we are here for WRITING_IDLE, the app wrote directly into our backing
|
|
// store memory. Just need to copy the data across to GL, no other work needed
|
|
void *ptr = m_Real.glMapNamedBufferRangeEXT(buffer, (GLintptr)record->Map.offset, GLsizeiptr(record->Map.length), GL_MAP_WRITE_BIT);
|
|
memcpy(ptr, record->Map.ptr, record->Map.length);
|
|
m_Real.glUnmapNamedBufferEXT(buffer);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// keep list of persistent & coherent maps up to date if we've
|
|
// made the last unmap to a buffer
|
|
if(record->Map.access & GL_MAP_PERSISTENT_BIT)
|
|
{
|
|
int64_t ref = Atomic::Dec64(&record->Map.persistentMaps);
|
|
if(ref == 0)
|
|
{
|
|
m_PersistentMaps.erase(record);
|
|
if(record->Map.access & GL_MAP_COHERENT_BIT)
|
|
m_CoherentMaps.erase(record);
|
|
}
|
|
}
|
|
|
|
record->Map.status = GLResourceRecord::Unmapped;
|
|
|
|
return ret;
|
|
}
|
|
|
|
return m_Real.glUnmapNamedBufferEXT(buffer);
|
|
}
|
|
|
|
GLboolean WrappedOpenGL::glUnmapBuffer(GLenum target)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
if(record)
|
|
return glUnmapNamedBufferEXT( record->Resource.name );
|
|
|
|
RDCERR("glUnmapBuffer: Couldn't get resource record for target %x - no buffer bound?", target);
|
|
}
|
|
|
|
return m_Real.glUnmapBuffer(target);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glFlushMappedNamedBufferRangeEXT(GLuint buffer, GLintptr offset, GLsizeiptr length)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
GLResourceRecord *record = NULL;
|
|
|
|
if(m_State >= WRITING)
|
|
record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
|
|
SERIALISE_ELEMENT(ResourceId, ID, record->GetResourceID());
|
|
SERIALISE_ELEMENT(uint64_t, offs, offset);
|
|
SERIALISE_ELEMENT(uint64_t, len, length);
|
|
|
|
// serialise out the flushed chunk of the shadow pointer
|
|
SERIALISE_ELEMENT_BUF(byte *, data, record->Map.ptr+offs, (size_t)len);
|
|
|
|
// update the comparison buffer in case this buffer is subsequently mapped and we want to find
|
|
// the difference region
|
|
if(m_State == WRITING_CAPFRAME && record->GetShadowPtr(1))
|
|
{
|
|
memcpy(record->GetShadowPtr(1)+offs, record->Map.ptr+offs, (size_t)len);
|
|
}
|
|
|
|
GLResource res;
|
|
|
|
if(m_State < WRITING)
|
|
res = GetResourceManager()->GetLiveResource(ID);
|
|
else
|
|
res = GetResourceManager()->GetCurrentResource(ID);
|
|
|
|
if(record && record->Map.persistentPtr)
|
|
{
|
|
// if we have a persistent mapped pointer, copy the range into the 'real' memory and
|
|
// do a flush. Note the persistent pointer is always to the base of the buffer so we
|
|
// need to account for the offset
|
|
|
|
memcpy(record->Map.persistentPtr+offs, record->Map.ptr - record->Map.offset + offs, (size_t)len);
|
|
m_Real.glFlushMappedNamedBufferRangeEXT(buffer, (GLintptr)offs, (GLsizeiptr)len);
|
|
}
|
|
else
|
|
{
|
|
// perform a map of the range and copy the data, to emulate the modified region being flushed
|
|
void *ptr = m_Real.glMapNamedBufferRangeEXT(res.name, (GLintptr)offs, (GLsizeiptr)len, GL_MAP_WRITE_BIT);
|
|
memcpy(ptr, data, (size_t)len);
|
|
m_Real.glUnmapNamedBufferEXT(res.name);
|
|
}
|
|
|
|
if(m_State < WRITING)
|
|
SAFE_DELETE_ARRAY(data);
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glFlushMappedNamedBufferRangeEXT(GLuint buffer, GLintptr offset, GLsizeiptr length)
|
|
{
|
|
// see above glMapNamedBufferRangeEXT for high-level explanation of how mapping is handled
|
|
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
RDCASSERT(record);
|
|
|
|
// only need to pay attention to flushes when in capframe. Otherwise (see above) we
|
|
// treat the map as a normal map, and let ALL modified regions go through, flushed or not,
|
|
// as this is legal - modified but unflushed regions are 'undefined' so we can just say
|
|
// that modifications applying is our undefined behaviour.
|
|
|
|
// note that we only want to flush the range with GL if we've actually
|
|
// mapped it. Otherwise the map is 'virtual' and just pointing to our backing store data
|
|
if(record && record->Map.status == GLResourceRecord::Mapped_Ignore_Real)
|
|
{
|
|
m_Real.glFlushMappedNamedBufferRangeEXT(buffer, offset, length);
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
if(record)
|
|
{
|
|
if(record->Map.status == GLResourceRecord::Unmapped)
|
|
{
|
|
RDCWARN("Unmapped buffer being flushed, ignoring");
|
|
}
|
|
else if(record->Map.status == GLResourceRecord::Mapped_Ignore_Real)
|
|
{
|
|
RDCERR("Failed to cap frame - uncapped Map/Unmap being flushed");
|
|
}
|
|
else if(record->Map.status == GLResourceRecord::Mapped_Write)
|
|
{
|
|
if(offset < record->Map.offset || offset + length > record->Map.offset + record->Map.length)
|
|
{
|
|
RDCWARN("Flushed buffer range is outside of mapped range, clamping");
|
|
|
|
// maintain the length/end boundary of the flushed range if the flushed offset
|
|
// is below the mapped range
|
|
if(offset < record->Map.offset)
|
|
{
|
|
offset += (record->Map.offset-offset);
|
|
length -= (record->Map.offset-offset);
|
|
}
|
|
|
|
// clamp the length if it's beyond the mapped range.
|
|
if(offset + length > record->Map.offset + record->Map.length)
|
|
{
|
|
length = (record->Map.offset + record->Map.length - offset);
|
|
}
|
|
}
|
|
|
|
SCOPED_SERIALISE_CONTEXT(FLUSHMAP);
|
|
Serialise_glFlushMappedNamedBufferRangeEXT(buffer, offset, length);
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
// other statuses is GLResourceRecord::Mapped_Read
|
|
}
|
|
}
|
|
else if(m_State == WRITING_IDLE)
|
|
{
|
|
// if this is a flush of a persistent map, we need to copy through to
|
|
// the real pointer and perform a real flush.
|
|
if(record && record->Map.persistentPtr)
|
|
{
|
|
memcpy(record->Map.persistentPtr+offset, record->Map.ptr - record->Map.offset + offset, length);
|
|
m_Real.glFlushMappedNamedBufferRangeEXT(buffer, offset, length);
|
|
|
|
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glFlushMappedNamedBufferRange(GLuint buffer, GLintptr offset, GLsizei length)
|
|
{
|
|
// only difference to EXT function is size parameter, so just upcast
|
|
glFlushMappedNamedBufferRangeEXT(buffer, offset, (GLsizeiptr)length);
|
|
}
|
|
|
|
void WrappedOpenGL::glFlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length)
|
|
{
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
|
|
RDCASSERT(record);
|
|
|
|
if(record)
|
|
return glFlushMappedNamedBufferRangeEXT(record->Resource.name, offset, length);
|
|
|
|
RDCERR("glFlushMappedBufferRange: Couldn't get resource record for target %x - no buffer bound?", target);
|
|
}
|
|
|
|
return m_Real.glFlushMappedBufferRange(target, offset, length);
|
|
}
|
|
|
|
void WrappedOpenGL::PersistentMapMemoryBarrier(const set<GLResourceRecord *> &maps)
|
|
{
|
|
// this function iterates over all the maps, checking for any changes between
|
|
// the shadow pointers, and propogates that to 'real' GL
|
|
|
|
for(set<GLResourceRecord *>::const_iterator it = maps.begin(); it != maps.end(); ++it)
|
|
{
|
|
GLResourceRecord *record = *it;
|
|
|
|
RDCASSERT(record && record->Map.persistentPtr);
|
|
|
|
size_t diffStart = 0, diffEnd = 0;
|
|
bool found = FindDiffRange(record->GetShadowPtr(0), record->GetShadowPtr(1), (size_t)record->Length, diffStart, diffEnd);
|
|
if(found)
|
|
{
|
|
// update the modified region in the 'comparison' shadow buffer for next check
|
|
memcpy(record->GetShadowPtr(1) + diffStart, record->GetShadowPtr(0) + diffStart, diffEnd - diffStart);
|
|
|
|
// we use our own flush function so it will serialise chunks when necessary, and it
|
|
// also handles copying into the persistent mapped pointer and flushing the real GL
|
|
// buffer
|
|
glFlushMappedNamedBufferRangeEXT(record->Resource.name, GLintptr(diffStart), GLsizeiptr(diffEnd - diffStart));
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Transform Feedback
|
|
|
|
bool WrappedOpenGL::Serialise_glGenTransformFeedbacks(GLsizei n, GLuint* ids)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(FeedbackRes(GetCtx(), *ids)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glGenTransformFeedbacks(1, &real);
|
|
m_Real.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, real);
|
|
m_Real.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, 0);
|
|
|
|
GLResource res = FeedbackRes(GetCtx(), real);
|
|
|
|
m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glGenTransformFeedbacks(GLsizei n, GLuint *ids)
|
|
{
|
|
m_Real.glGenTransformFeedbacks(n, ids);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = FeedbackRes(GetCtx(), ids[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(GEN_FEEDBACK);
|
|
Serialise_glGenTransformFeedbacks(1, ids+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glCreateTransformFeedbacks(GLsizei n, GLuint* ids)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(FeedbackRes(GetCtx(), *ids)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glCreateTransformFeedbacks(1, &real);
|
|
|
|
GLResource res = FeedbackRes(GetCtx(), real);
|
|
|
|
m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glCreateTransformFeedbacks(GLsizei n, GLuint *ids)
|
|
{
|
|
m_Real.glCreateTransformFeedbacks(n, ids);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = FeedbackRes(GetCtx(), ids[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(CREATE_FEEDBACK);
|
|
Serialise_glCreateTransformFeedbacks(1, ids+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glDeleteTransformFeedbacks(GLsizei n, const GLuint *ids)
|
|
{
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = FeedbackRes(GetCtx(), ids[i]);
|
|
if(GetResourceManager()->HasCurrentResource(res))
|
|
{
|
|
GetResourceManager()->MarkCleanResource(res);
|
|
if(GetResourceManager()->HasResourceRecord(res))
|
|
GetResourceManager()->GetResourceRecord(res)->Delete(GetResourceManager());
|
|
GetResourceManager()->UnregisterResource(res);
|
|
}
|
|
}
|
|
|
|
m_Real.glDeleteTransformFeedbacks(n, ids);
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glTransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, idx, index);
|
|
SERIALISE_ELEMENT(ResourceId, xid, GetResourceManager()->GetID(FeedbackRes(GetCtx(), xfb)));
|
|
SERIALISE_ELEMENT(ResourceId, bid, GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
xfb = GetResourceManager()->GetLiveResource(xid).name;
|
|
|
|
// use ARB_direct_state_access functions here as we use EXT_direct_state_access elsewhere. If
|
|
// we are running without ARB_dsa support, these functions are emulated in the trivial way. This is
|
|
// necessary since these functions can be serialised even if ARB_dsa was not used originally, and
|
|
// we need to support this case.
|
|
if(bid == ResourceId())
|
|
m_Real.glTransformFeedbackBufferBase(xfb, idx, 0);
|
|
else
|
|
m_Real.glTransformFeedbackBufferBase(xfb, idx, GetResourceManager()->GetLiveResource(bid).name);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glTransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer)
|
|
{
|
|
m_Real.glTransformFeedbackBufferBase(xfb, index, buffer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_BASE);
|
|
Serialise_glTransformFeedbackBufferBase(xfb, index, buffer);
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
else if(xfb != 0)
|
|
{
|
|
GLResourceRecord *fbrecord = GetResourceManager()->GetResourceRecord(FeedbackRes(GetCtx(), xfb));
|
|
|
|
fbrecord->AddChunk(scope.Get());
|
|
|
|
if(buffer != 0)
|
|
fbrecord->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glTransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizei size)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, idx, index);
|
|
SERIALISE_ELEMENT(ResourceId, xid, GetResourceManager()->GetID(FeedbackRes(GetCtx(), xfb)));
|
|
SERIALISE_ELEMENT(ResourceId, bid, GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)));
|
|
SERIALISE_ELEMENT(uint64_t, offs, (uint64_t)offset);
|
|
SERIALISE_ELEMENT(uint64_t, sz, (uint64_t)size);
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
xfb = GetResourceManager()->GetLiveResource(xid).name;
|
|
|
|
// use ARB_direct_state_access functions here as we use EXT_direct_state_access elsewhere. If
|
|
// we are running without ARB_dsa support, these functions are emulated in the obvious way. This is
|
|
// necessary since these functions can be serialised even if ARB_dsa was not used originally, and
|
|
// we need to support this case.
|
|
if(bid == ResourceId())
|
|
m_Real.glTransformFeedbackBufferBase(xfb, idx, 0); // if we're unbinding, offset/size don't matter
|
|
else
|
|
m_Real.glTransformFeedbackBufferRange(xfb, idx, GetResourceManager()->GetLiveResource(bid).name, (GLintptr)offs, (GLsizei)sz);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glTransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizei size)
|
|
{
|
|
m_Real.glTransformFeedbackBufferRange(xfb, index, buffer, offset, size);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(FEEDBACK_BUFFER_RANGE);
|
|
Serialise_glTransformFeedbackBufferRange(xfb, index, buffer, offset, size);
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
else if(xfb != 0)
|
|
{
|
|
GLResourceRecord *fbrecord = GetResourceManager()->GetResourceRecord(FeedbackRes(GetCtx(), xfb));
|
|
|
|
fbrecord->AddChunk(scope.Get());
|
|
|
|
if(buffer != 0)
|
|
fbrecord->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindTransformFeedback(GLenum target, GLuint id)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Target, target);
|
|
SERIALISE_ELEMENT(ResourceId, fid, GetResourceManager()->GetID(FeedbackRes(GetCtx(), id)));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(fid != ResourceId())
|
|
m_Real.glBindTransformFeedback(Target, GetResourceManager()->GetLiveResource(fid).name);
|
|
else
|
|
m_Real.glBindTransformFeedback(Target, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindTransformFeedback(GLenum target, GLuint id)
|
|
{
|
|
m_Real.glBindTransformFeedback(target, id);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
if(id == 0)
|
|
{
|
|
GetCtxData().m_FeedbackRecord = NULL;
|
|
}
|
|
else
|
|
{
|
|
GetCtxData().m_FeedbackRecord = GetResourceManager()->GetResourceRecord(FeedbackRes(GetCtx(), id));
|
|
}
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_FEEDBACK);
|
|
Serialise_glBindTransformFeedback(target, id);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBeginTransformFeedback(GLenum primitiveMode)
|
|
{
|
|
SERIALISE_ELEMENT(GLenum, Mode, primitiveMode);
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glBeginTransformFeedback(Mode);
|
|
m_ActiveFeedback = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBeginTransformFeedback(GLenum primitiveMode)
|
|
{
|
|
m_Real.glBeginTransformFeedback(primitiveMode);
|
|
m_ActiveFeedback = true;
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BEGIN_FEEDBACK);
|
|
Serialise_glBeginTransformFeedback(primitiveMode);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glPauseTransformFeedback()
|
|
{
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glPauseTransformFeedback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glPauseTransformFeedback()
|
|
{
|
|
m_Real.glPauseTransformFeedback();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(PAUSE_FEEDBACK);
|
|
Serialise_glPauseTransformFeedback();
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glResumeTransformFeedback()
|
|
{
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glResumeTransformFeedback();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glResumeTransformFeedback()
|
|
{
|
|
m_Real.glResumeTransformFeedback();
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(RESUME_FEEDBACK);
|
|
Serialise_glResumeTransformFeedback();
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glEndTransformFeedback()
|
|
{
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
m_Real.glEndTransformFeedback();
|
|
m_ActiveFeedback = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glEndTransformFeedback()
|
|
{
|
|
m_Real.glEndTransformFeedback();
|
|
m_ActiveFeedback = false;
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(END_FEEDBACK);
|
|
Serialise_glEndTransformFeedback();
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Vertex Arrays
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint8_t, Norm, normalized);
|
|
SERIALISE_ELEMENT(uint32_t, Stride, stride);
|
|
SERIALISE_ELEMENT(uint64_t, Offset, (uint64_t)offset);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
SERIALISE_ELEMENT(ResourceId, bid, buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
buffer = (bid != ResourceId()) ? GetResourceManager()->GetLiveResource(bid).name : 0;
|
|
|
|
// seems buggy when mixed and matched with new style vertex attrib binding, which we use for VAO initial states.
|
|
// Since the spec defines how this function should work in terms of new style bindings, just do that ourselves.
|
|
//m_Real.glVertexArrayVertexAttribOffsetEXT(vaobj, buffer, Index, Size, Type, Norm, Stride, (GLintptr)Offset);
|
|
m_Real.glVertexArrayVertexAttribFormatEXT(vaobj, Index, Size, Type, Norm, 0);
|
|
m_Real.glVertexArrayVertexAttribBindingEXT(vaobj, Index, Index);
|
|
if(Stride == 0)
|
|
{
|
|
GLenum SizeEnum = Size == 1 ? eGL_RED :
|
|
Size == 2 ? eGL_RG :
|
|
Size == 3 ? eGL_RGB :
|
|
eGL_RGBA;
|
|
Stride = (uint32_t)GetByteSize(1, 1, 1, SizeEnum, Type);
|
|
}
|
|
m_Real.glVertexArrayBindVertexBufferEXT(vaobj, Index, buffer, (GLintptr)Offset, Stride);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribOffsetEXT(vaobj, buffer, index, size, type, normalized, stride, offset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBPOINTER);
|
|
Serialise_glVertexArrayVertexAttribOffsetEXT(vaobj, buffer, index, size, type, normalized, stride, offset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)
|
|
{
|
|
m_Real.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = cd.m_BufferRecord[BufferIdx(eGL_ARRAY_BUFFER)];
|
|
GLResourceRecord *varecord = cd.m_VertexArrayRecord;
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBPOINTER);
|
|
Serialise_glVertexArrayVertexAttribOffsetEXT(varecord ? varecord->Resource.name : 0, bufrecord ? bufrecord->Resource.name : 0,
|
|
index, size, type, normalized, stride, (GLintptr)pointer);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribIOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint32_t, Stride, stride);
|
|
SERIALISE_ELEMENT(uint64_t, Offset, (uint64_t)offset);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
SERIALISE_ELEMENT(ResourceId, bid, buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
buffer = (bid != ResourceId()) ? GetResourceManager()->GetLiveResource(bid).name : 0;
|
|
|
|
// seems buggy when mixed and matched with new style vertex attrib binding, which we use for VAO initial states.
|
|
// Since the spec defines how this function should work in terms of new style bindings, just do that ourselves.
|
|
//m_Real.glVertexArrayVertexAttribIOffsetEXT(vaobj, buffer, Index, Size, Type, Stride, (GLintptr)Offset);
|
|
m_Real.glVertexArrayVertexAttribIFormatEXT(vaobj, Index, Size, Type, 0);
|
|
m_Real.glVertexArrayVertexAttribBindingEXT(vaobj, Index, Index);
|
|
if(Stride == 0)
|
|
{
|
|
GLenum SizeEnum = Size == 1 ? eGL_RED :
|
|
Size == 2 ? eGL_RG :
|
|
Size == 3 ? eGL_RGB :
|
|
eGL_RGBA;
|
|
Stride = (uint32_t)GetByteSize(1, 1, 1, SizeEnum, Type);
|
|
}
|
|
m_Real.glVertexArrayBindVertexBufferEXT(vaobj, Index, buffer, (GLintptr)Offset, Stride);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribIOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribIOffsetEXT(vaobj, buffer, index, size, type, stride, offset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBIPOINTER);
|
|
Serialise_glVertexArrayVertexAttribIOffsetEXT(vaobj, buffer, index, size, type, stride, offset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer)
|
|
{
|
|
m_Real.glVertexAttribIPointer(index, size, type, stride, pointer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = cd.m_BufferRecord[BufferIdx(eGL_ARRAY_BUFFER)];
|
|
GLResourceRecord *varecord = cd.m_VertexArrayRecord;
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBIPOINTER);
|
|
Serialise_glVertexArrayVertexAttribIOffsetEXT(varecord ? varecord->Resource.name : 0, bufrecord ? bufrecord->Resource.name : 0,
|
|
index, size, type, stride, (GLintptr)pointer);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribLOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr pointer)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint32_t, Stride, stride);
|
|
SERIALISE_ELEMENT(uint64_t, Offset, (uint64_t)pointer);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
SERIALISE_ELEMENT(ResourceId, bid, buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
buffer = (bid != ResourceId()) ? GetResourceManager()->GetLiveResource(bid).name : 0;
|
|
|
|
// seems buggy when mixed and matched with new style vertex attrib binding, which we use for VAO initial states.
|
|
// Since the spec defines how this function should work in terms of new style bindings, just do that ourselves.
|
|
//m_Real.glVertexArrayVertexAttribLOffsetEXT(vaobj, buffer, Index, Size, Type, Stride, (GLintptr)Offset);
|
|
m_Real.glVertexArrayVertexAttribLFormatEXT(vaobj, Index, Size, Type, 0);
|
|
m_Real.glVertexArrayVertexAttribBindingEXT(vaobj, Index, Index);
|
|
if(Stride == 0)
|
|
{
|
|
GLenum SizeEnum = Size == 1 ? eGL_RED :
|
|
Size == 2 ? eGL_RG :
|
|
Size == 3 ? eGL_RGB :
|
|
eGL_RGBA;
|
|
Stride = (uint32_t)GetByteSize(1, 1, 1, SizeEnum, Type);
|
|
}
|
|
m_Real.glVertexArrayBindVertexBufferEXT(vaobj, Index, buffer, (GLintptr)Offset, Stride);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribLOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr pointer)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribLOffsetEXT(vaobj, buffer, index, size, type, stride, pointer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBLPOINTER);
|
|
Serialise_glVertexArrayVertexAttribLOffsetEXT(vaobj, buffer, index, size, type, stride, pointer);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer)
|
|
{
|
|
m_Real.glVertexAttribLPointer(index, size, type, stride, pointer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
ContextData &cd = GetCtxData();
|
|
GLResourceRecord *bufrecord = cd.m_BufferRecord[BufferIdx(eGL_ARRAY_BUFFER)];
|
|
GLResourceRecord *varecord = cd.m_VertexArrayRecord;
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBLPOINTER);
|
|
Serialise_glVertexArrayVertexAttribLOffsetEXT(varecord ? varecord->Resource.name : 0, bufrecord ? bufrecord->Resource.name : 0,
|
|
index, size, type, stride, (GLintptr)pointer);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(bufrecord) r->AddParent(bufrecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribBindingEXT(GLuint vaobj, GLuint attribindex, GLuint bindingindex)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, aidx, attribindex);
|
|
SERIALISE_ELEMENT(uint32_t, bidx, bindingindex);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribBindingEXT((id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO, aidx, bidx);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribBindingEXT(GLuint vaobj, GLuint attribindex, GLuint bindingindex)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribBindingEXT(vaobj, attribindex, bindingindex);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBBINDING);
|
|
Serialise_glVertexArrayVertexAttribBindingEXT(vaobj, attribindex, bindingindex);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribBinding(GLuint attribindex, GLuint bindingindex)
|
|
{
|
|
m_Real.glVertexAttribBinding(attribindex, bindingindex);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBBINDING);
|
|
Serialise_glVertexArrayVertexAttribBindingEXT(varecord ? varecord->Resource.name : 0, attribindex, bindingindex);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, attribindex);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(bool, Norm, normalized ? true : false);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint32_t, Offset, relativeoffset);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
|
|
m_Real.glVertexArrayVertexAttribFormatEXT(vaobj, Index, Size, Type, Norm, Offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribFormatEXT(vaobj, attribindex, size, type, normalized, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBFORMAT);
|
|
Serialise_glVertexArrayVertexAttribFormatEXT(vaobj, attribindex, size, type, normalized, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexAttribFormat(attribindex, size, type, normalized, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBFORMAT);
|
|
Serialise_glVertexArrayVertexAttribFormatEXT(varecord ? varecord->Resource.name : 0, attribindex, size, type, normalized, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribIFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, attribindex);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint32_t, Offset, relativeoffset);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
|
|
m_Real.glVertexArrayVertexAttribIFormatEXT(vaobj, Index, Size, Type, Offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribIFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribIFormatEXT(vaobj, attribindex, size, type, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBIFORMAT);
|
|
Serialise_glVertexArrayVertexAttribIFormatEXT(vaobj, attribindex, size, type, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexAttribIFormat(attribindex, size, type, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBIFORMAT);
|
|
Serialise_glVertexArrayVertexAttribIFormatEXT(varecord ? varecord->Resource.name : 0, attribindex, size, type, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribLFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, attribindex);
|
|
SERIALISE_ELEMENT(int32_t, Size, size);
|
|
SERIALISE_ELEMENT(GLenum, Type, type);
|
|
SERIALISE_ELEMENT(uint32_t, Offset, relativeoffset);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
|
|
m_Real.glVertexArrayVertexAttribLFormatEXT(vaobj, Index, Size, Type, Offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribLFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribLFormatEXT(vaobj, attribindex, size, type, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBLFORMAT);
|
|
Serialise_glVertexArrayVertexAttribLFormatEXT(vaobj, attribindex, size, type, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset)
|
|
{
|
|
m_Real.glVertexAttribLFormat(attribindex, size, type, relativeoffset);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBLFORMAT);
|
|
Serialise_glVertexArrayVertexAttribLFormatEXT(varecord ? varecord->Resource.name : 0, attribindex, size, type, relativeoffset);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexAttribDivisorEXT(GLuint vaobj, GLuint index, GLuint divisor)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(uint32_t, Divisor, divisor);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
vaobj = (id != ResourceId()) ? GetResourceManager()->GetLiveResource(id).name : m_FakeVAO;
|
|
|
|
// at the time of writing, AMD driver seems to not have this entry point
|
|
if(m_Real.glVertexArrayVertexAttribDivisorEXT)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribDivisorEXT(vaobj, Index, Divisor);
|
|
}
|
|
else
|
|
{
|
|
GLuint VAO = 0;
|
|
m_Real.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&VAO);
|
|
m_Real.glBindVertexArray(vaobj);
|
|
m_Real.glVertexAttribDivisor(Index, Divisor);
|
|
m_Real.glBindVertexArray(VAO);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexAttribDivisorEXT(GLuint vaobj, GLuint index, GLuint divisor)
|
|
{
|
|
m_Real.glVertexArrayVertexAttribDivisorEXT(vaobj, index, divisor);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBDIVISOR);
|
|
Serialise_glVertexArrayVertexAttribDivisorEXT(vaobj, index, divisor);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexAttribDivisor(GLuint index, GLuint divisor)
|
|
{
|
|
m_Real.glVertexAttribDivisor(index, divisor);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIBDIVISOR);
|
|
Serialise_glVertexArrayVertexAttribDivisorEXT(varecord ? varecord->Resource.name : 0, index, divisor);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glEnableVertexArrayAttribEXT(GLuint vaobj, GLuint index)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
if(m_State == READING)
|
|
{
|
|
if(id != ResourceId())
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glBindVertexArray(res.name);
|
|
}
|
|
else
|
|
{
|
|
m_Real.glBindVertexArray(m_FakeVAO);
|
|
}
|
|
}
|
|
|
|
m_Real.glEnableVertexAttribArray(Index);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glEnableVertexArrayAttribEXT(GLuint vaobj, GLuint index)
|
|
{
|
|
m_Real.glEnableVertexArrayAttribEXT(vaobj, index);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(ENABLEVERTEXATTRIBARRAY);
|
|
Serialise_glEnableVertexArrayAttribEXT(vaobj, index);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glEnableVertexAttribArray(GLuint index)
|
|
{
|
|
m_Real.glEnableVertexAttribArray(index);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(ENABLEVERTEXATTRIBARRAY);
|
|
Serialise_glEnableVertexArrayAttribEXT(varecord ? varecord->Resource.name : 0, index);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glDisableVertexArrayAttribEXT(GLuint vaobj, GLuint index)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, Index, index);
|
|
SERIALISE_ELEMENT(ResourceId, id, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State < WRITING)
|
|
{
|
|
if(m_State == READING)
|
|
{
|
|
if(id != ResourceId())
|
|
{
|
|
GLResource res = GetResourceManager()->GetLiveResource(id);
|
|
m_Real.glBindVertexArray(res.name);
|
|
}
|
|
else
|
|
{
|
|
m_Real.glBindVertexArray(m_FakeVAO);
|
|
}
|
|
}
|
|
|
|
m_Real.glDisableVertexAttribArray(Index);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glDisableVertexArrayAttribEXT(GLuint vaobj, GLuint index)
|
|
{
|
|
m_Real.glDisableVertexArrayAttribEXT(vaobj, index);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(DISABLEVERTEXATTRIBARRAY);
|
|
Serialise_glDisableVertexArrayAttribEXT(vaobj, index);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glDisableVertexAttribArray(GLuint index)
|
|
{
|
|
m_Real.glDisableVertexAttribArray(index);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(DISABLEVERTEXATTRIBARRAY);
|
|
Serialise_glDisableVertexArrayAttribEXT(varecord ? varecord->Resource.name : 0, index);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glGenVertexArrays(GLsizei n, GLuint* arrays)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(VertexArrayRes(GetCtx(), *arrays)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glGenVertexArrays(1, &real);
|
|
m_Real.glBindVertexArray(real);
|
|
m_Real.glBindVertexArray(0);
|
|
|
|
GLResource res = VertexArrayRes(GetCtx(), real);
|
|
|
|
m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glGenVertexArrays(GLsizei n, GLuint *arrays)
|
|
{
|
|
m_Real.glGenVertexArrays(n, arrays);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = VertexArrayRes(GetCtx(), arrays[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(GEN_VERTEXARRAY);
|
|
Serialise_glGenVertexArrays(1, arrays+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glCreateVertexArrays(GLsizei n, GLuint* arrays)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(VertexArrayRes(GetCtx(), *arrays)));
|
|
|
|
if(m_State == READING)
|
|
{
|
|
GLuint real = 0;
|
|
m_Real.glCreateVertexArrays(1, &real);
|
|
|
|
GLResource res = VertexArrayRes(GetCtx(), real);
|
|
|
|
m_ResourceManager->RegisterResource(res);
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glCreateVertexArrays(GLsizei n, GLuint *arrays)
|
|
{
|
|
m_Real.glCreateVertexArrays(n, arrays);
|
|
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = VertexArrayRes(GetCtx(), arrays[i]);
|
|
ResourceId id = GetResourceManager()->RegisterResource(res);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
Chunk *chunk = NULL;
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(CREATE_VERTEXARRAY);
|
|
Serialise_glCreateVertexArrays(1, arrays+i);
|
|
|
|
chunk = scope.Get();
|
|
}
|
|
|
|
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
|
|
RDCASSERT(record);
|
|
|
|
record->AddChunk(chunk);
|
|
}
|
|
else
|
|
{
|
|
GetResourceManager()->AddLiveResource(id, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glBindVertexArray(GLuint array)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, (array ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), array)) : ResourceId()));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(id == ResourceId())
|
|
{
|
|
m_Real.glBindVertexArray(m_FakeVAO);
|
|
}
|
|
else
|
|
{
|
|
GLuint live = GetResourceManager()->GetLiveResource(id).name;
|
|
m_Real.glBindVertexArray(live);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glBindVertexArray(GLuint array)
|
|
{
|
|
m_Real.glBindVertexArray(array);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
if(array == 0)
|
|
{
|
|
GetCtxData().m_VertexArrayRecord = NULL;
|
|
}
|
|
else
|
|
{
|
|
GetCtxData().m_VertexArrayRecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), array));
|
|
}
|
|
}
|
|
|
|
if(m_State == WRITING_CAPFRAME)
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_VERTEXARRAY);
|
|
Serialise_glBindVertexArray(array);
|
|
|
|
m_ContextRecord->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayElementBuffer(GLuint vaobj, GLuint buffer)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, vid, (vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId()));
|
|
SERIALISE_ELEMENT(ResourceId, bid, (buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId()));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
vaobj = 0;
|
|
if(vid != ResourceId()) vaobj = GetResourceManager()->GetLiveResource(vid).name;
|
|
|
|
buffer = 0;
|
|
if(bid != ResourceId()) buffer = GetResourceManager()->GetLiveResource(bid).name;
|
|
|
|
// use ARB_direct_state_access functions here as we use EXT_direct_state_access elsewhere. If
|
|
// we are running without ARB_dsa support, these functions are emulated in the obvious way. This is
|
|
// necessary since these functions can be serialised even if ARB_dsa was not used originally, and
|
|
// we need to support this case.
|
|
m_Real.glVertexArrayElementBuffer(vaobj, buffer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayElementBuffer(GLuint vaobj, GLuint buffer)
|
|
{
|
|
m_Real.glVertexArrayElementBuffer(vaobj, buffer);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VAO_ELEMENT_BUFFER);
|
|
Serialise_glVertexArrayElementBuffer(vaobj, buffer);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(buffer != 0)
|
|
r->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayBindVertexBufferEXT(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, idx, bindingindex);
|
|
SERIALISE_ELEMENT(ResourceId, id, (buffer ? GetResourceManager()->GetID(BufferRes(GetCtx(), buffer)) : ResourceId()));
|
|
SERIALISE_ELEMENT(uint64_t, offs, offset);
|
|
SERIALISE_ELEMENT(uint64_t, str, stride);
|
|
SERIALISE_ELEMENT(ResourceId, vid, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
vaobj = (vid != ResourceId()) ? GetResourceManager()->GetLiveResource(vid).name : m_FakeVAO;
|
|
|
|
GLuint live = 0;
|
|
if(id != ResourceId())
|
|
live = GetResourceManager()->GetLiveResource(id).name;
|
|
|
|
m_Real.glVertexArrayBindVertexBufferEXT(vaobj, idx, live, (GLintptr)offs, (GLsizei)str);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayBindVertexBufferEXT(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride)
|
|
{
|
|
m_Real.glVertexArrayBindVertexBufferEXT(vaobj, bindingindex, buffer, offset, stride);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_VERTEXBUFFER);
|
|
Serialise_glVertexArrayBindVertexBufferEXT(vaobj, bindingindex, buffer, offset, stride);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(buffer != 0)
|
|
r->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glBindVertexBuffer(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride)
|
|
{
|
|
m_Real.glBindVertexBuffer(bindingindex, buffer, offset, stride);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_VERTEXBUFFER);
|
|
Serialise_glVertexArrayBindVertexBufferEXT(varecord ? varecord->Resource.name : 0, bindingindex, buffer, offset, stride);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
if(buffer != 0)
|
|
r->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, First, first);
|
|
SERIALISE_ELEMENT(int32_t, Count, count);
|
|
SERIALISE_ELEMENT(ResourceId, vid, vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId());
|
|
|
|
GLuint *bufs = NULL;
|
|
GLintptr *offs = NULL;
|
|
GLsizei *str = NULL;
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
bufs = new GLuint[Count];
|
|
offs = new GLintptr[Count];
|
|
str = new GLsizei[Count];
|
|
}
|
|
|
|
for(int32_t i=0; i < Count; i++)
|
|
{
|
|
SERIALISE_ELEMENT(ResourceId, id, GetResourceManager()->GetID(BufferRes(GetCtx(), buffers[i])));
|
|
SERIALISE_ELEMENT(uint64_t, offset, (uint64_t)offsets[i]);
|
|
SERIALISE_ELEMENT(uint64_t, stride, (uint64_t)strides[i]);
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(id != ResourceId())
|
|
bufs[i] = GetResourceManager()->GetLiveResource(id).name;
|
|
else
|
|
bufs[i] = 0;
|
|
offs[i] = (GLintptr)offset;
|
|
str[i] = (GLsizei)stride;
|
|
}
|
|
}
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
if(vid != ResourceId())
|
|
vaobj = GetResourceManager()->GetLiveResource(vid).name;
|
|
else
|
|
vaobj = m_FakeVAO;
|
|
|
|
// use ARB_direct_state_access functions here as we use EXT_direct_state_access elsewhere. If
|
|
// we are running without ARB_dsa support, these functions are emulated in the obvious way. This is
|
|
// necessary since these functions can be serialised even if ARB_dsa was not used originally, and
|
|
// we need to support this case.
|
|
m_Real.glVertexArrayVertexBuffers(vaobj, First, Count, bufs, offs, str);
|
|
|
|
delete[] bufs;
|
|
delete[] offs;
|
|
delete[] str;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides)
|
|
{
|
|
m_Real.glVertexArrayVertexBuffers(vaobj, first, count, buffers, offsets, strides);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_VERTEXBUFFERS);
|
|
Serialise_glVertexArrayVertexBuffers(vaobj, first, count, buffers, offsets, strides);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
for(GLsizei i=0; i < count; i++)
|
|
{
|
|
if(buffers[i] != 0)
|
|
r->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[i])));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glBindVertexBuffers(GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides)
|
|
{
|
|
m_Real.glBindVertexBuffers(first, count, buffers, offsets, strides);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(BIND_VERTEXBUFFERS);
|
|
Serialise_glVertexArrayVertexBuffers(varecord ? varecord->Resource.name : 0, first, count, buffers, offsets, strides);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
|
|
for(GLsizei i=0; i < count; i++)
|
|
{
|
|
if(buffers[i] != 0)
|
|
r->AddParent(GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffers[i])));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexArrayVertexBindingDivisorEXT(GLuint vaobj, GLuint bindingindex, GLuint divisor)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, idx, bindingindex);
|
|
SERIALISE_ELEMENT(uint32_t, d, divisor);
|
|
SERIALISE_ELEMENT(ResourceId, vid, (vaobj ? GetResourceManager()->GetID(VertexArrayRes(GetCtx(), vaobj)) : ResourceId()));
|
|
|
|
if(m_State <= EXECUTING)
|
|
{
|
|
vaobj = (vid != ResourceId()) ? GetResourceManager()->GetLiveResource(vid).name : m_FakeVAO;
|
|
|
|
m_Real.glVertexArrayVertexBindingDivisorEXT(vaobj, idx, d);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexArrayVertexBindingDivisorEXT(GLuint vaobj, GLuint bindingindex, GLuint divisor)
|
|
{
|
|
m_Real.glVertexArrayVertexBindingDivisorEXT(vaobj, bindingindex, divisor);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetResourceManager()->GetResourceRecord(VertexArrayRes(GetCtx(), vaobj));
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXBINDINGDIVISOR);
|
|
Serialise_glVertexArrayVertexBindingDivisorEXT(vaobj, bindingindex, divisor);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glVertexBindingDivisor(GLuint bindingindex, GLuint divisor)
|
|
{
|
|
m_Real.glVertexBindingDivisor(bindingindex, divisor);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
GLResourceRecord *varecord = GetCtxData().m_VertexArrayRecord;
|
|
|
|
GLResourceRecord *r = m_State == WRITING_CAPFRAME ? m_ContextRecord : varecord;
|
|
|
|
if(r)
|
|
{
|
|
if(m_State == WRITING_IDLE && !RecordUpdateCheck(varecord))
|
|
return;
|
|
if(m_State == WRITING_CAPFRAME && varecord)
|
|
GetResourceManager()->MarkResourceFrameReferenced(varecord->GetResourceID(), eFrameRef_Write);
|
|
|
|
{
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXBINDINGDIVISOR);
|
|
Serialise_glVertexArrayVertexBindingDivisorEXT(varecord ? varecord->Resource.name : 0, bindingindex, divisor);
|
|
|
|
r->AddChunk(scope.Get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::glDeleteBuffers(GLsizei n, const GLuint *buffers)
|
|
{
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = BufferRes(GetCtx(), buffers[i]);
|
|
if(GetResourceManager()->HasCurrentResource(res))
|
|
{
|
|
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(res);
|
|
if(record)
|
|
{
|
|
// if we have a persistent pointer, make sure to unmap it
|
|
if(record->Map.persistentPtr)
|
|
{
|
|
m_PersistentMaps.erase(record);
|
|
if(record->Map.access & GL_MAP_COHERENT_BIT)
|
|
m_CoherentMaps.erase(record);
|
|
|
|
m_Real.glUnmapNamedBufferEXT(res.name);
|
|
}
|
|
|
|
// free any shadow storage
|
|
record->FreeShadowStorage();
|
|
}
|
|
|
|
GetResourceManager()->MarkCleanResource(res);
|
|
if(GetResourceManager()->HasResourceRecord(res))
|
|
GetResourceManager()->GetResourceRecord(res)->Delete(GetResourceManager());
|
|
GetResourceManager()->UnregisterResource(res);
|
|
}
|
|
}
|
|
|
|
m_Real.glDeleteBuffers(n, buffers);
|
|
}
|
|
|
|
void WrappedOpenGL::glDeleteVertexArrays(GLsizei n, const GLuint *arrays)
|
|
{
|
|
for(GLsizei i=0; i < n; i++)
|
|
{
|
|
GLResource res = VertexArrayRes(GetCtx(), arrays[i]);
|
|
if(GetResourceManager()->HasCurrentResource(res))
|
|
{
|
|
GetResourceManager()->MarkCleanResource(res);
|
|
if(GetResourceManager()->HasResourceRecord(res))
|
|
GetResourceManager()->GetResourceRecord(res)->Delete(GetResourceManager());
|
|
GetResourceManager()->UnregisterResource(res);
|
|
}
|
|
}
|
|
|
|
m_Real.glDeleteVertexArrays(n, arrays);
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Horrible glVertexAttrib variants
|
|
|
|
bool WrappedOpenGL::Serialise_glVertexAttrib(GLuint index, int count, GLenum type, GLboolean normalized, const void *value, int attribtype)
|
|
{
|
|
SERIALISE_ELEMENT(uint32_t, idx, index);
|
|
SERIALISE_ELEMENT(int32_t, Count, count);
|
|
SERIALISE_ELEMENT(int, Type, attribtype);
|
|
SERIALISE_ELEMENT(bool, norm, normalized == GL_TRUE);
|
|
SERIALISE_ELEMENT(GLenum, packedType, type);
|
|
|
|
AttribType attr = AttribType(Type & Attrib_typemask);
|
|
|
|
size_t elemSize = 1;
|
|
switch(attr)
|
|
{
|
|
case Attrib_GLdouble:
|
|
elemSize = 8;
|
|
break;
|
|
case Attrib_GLfloat:
|
|
case Attrib_GLint:
|
|
case Attrib_GLuint:
|
|
case Attrib_packed:
|
|
elemSize = 4;
|
|
break;
|
|
case Attrib_GLshort:
|
|
case Attrib_GLushort:
|
|
elemSize = 2;
|
|
break;
|
|
default:
|
|
case Attrib_GLbyte:
|
|
case Attrib_GLubyte:
|
|
elemSize = 1;
|
|
break;
|
|
}
|
|
|
|
size_t valueSize = elemSize*Count;
|
|
if(Type == Attrib_packed)
|
|
valueSize = sizeof(uint32_t);
|
|
|
|
if(m_State >= WRITING)
|
|
{
|
|
m_pSerialiser->RawWriteBytes(value, valueSize);
|
|
}
|
|
else if(m_State <= EXECUTING)
|
|
{
|
|
value = m_pSerialiser->RawReadBytes(valueSize);
|
|
|
|
if(Type == Attrib_packed)
|
|
{
|
|
if(Count == 1) m_Real.glVertexAttribP1uiv(idx, packedType, norm, (GLuint*)value);
|
|
else if(Count == 2) m_Real.glVertexAttribP2uiv(idx, packedType, norm, (GLuint*)value);
|
|
else if(Count == 3) m_Real.glVertexAttribP3uiv(idx, packedType, norm, (GLuint*)value);
|
|
else if(Count == 4) m_Real.glVertexAttribP4uiv(idx, packedType, norm, (GLuint*)value);
|
|
}
|
|
else if(Type & Attrib_I)
|
|
{
|
|
if(Count == 1)
|
|
{
|
|
if(attr == Attrib_GLint) m_Real.glVertexAttribI1iv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttribI1uiv(idx, (GLuint*)value);
|
|
}
|
|
else if(Count == 2)
|
|
{
|
|
if(attr == Attrib_GLint) m_Real.glVertexAttribI2iv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttribI2uiv(idx, (GLuint*)value);
|
|
}
|
|
else if(Count == 2)
|
|
{
|
|
if(attr == Attrib_GLint) m_Real.glVertexAttribI3iv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttribI3uiv(idx, (GLuint*)value);
|
|
}
|
|
else
|
|
{
|
|
if(attr == Attrib_GLbyte) m_Real.glVertexAttribI4bv(idx, (GLbyte*)value);
|
|
else if(attr == Attrib_GLint) m_Real.glVertexAttribI4iv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttribI4sv(idx, (GLshort*)value);
|
|
else if(attr == Attrib_GLubyte) m_Real.glVertexAttribI4ubv(idx, (GLubyte*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttribI4uiv(idx, (GLuint*)value);
|
|
else if(attr == Attrib_GLushort) m_Real.glVertexAttribI4usv(idx, (GLushort*)value);
|
|
}
|
|
}
|
|
else if(Type & Attrib_L)
|
|
{
|
|
if(Count == 1) m_Real.glVertexAttribL1dv(idx, (GLdouble*)value);
|
|
else if(Count == 2) m_Real.glVertexAttribL2dv(idx, (GLdouble*)value);
|
|
else if(Count == 3) m_Real.glVertexAttribL3dv(idx, (GLdouble*)value);
|
|
else if(Count == 4) m_Real.glVertexAttribL4dv(idx, (GLdouble*)value);
|
|
}
|
|
else if(Type & Attrib_N)
|
|
{
|
|
if(attr == Attrib_GLbyte) m_Real.glVertexAttrib4Nbv(idx, (GLbyte*)value);
|
|
else if(attr == Attrib_GLint) m_Real.glVertexAttrib4Niv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttrib4Nsv(idx, (GLshort*)value);
|
|
else if(attr == Attrib_GLubyte) m_Real.glVertexAttrib4Nubv(idx, (GLubyte*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttrib4Nuiv(idx, (GLuint*)value);
|
|
else if(attr == Attrib_GLushort) m_Real.glVertexAttrib4Nusv(idx, (GLushort*)value);
|
|
}
|
|
else
|
|
{
|
|
if(Count == 1)
|
|
{
|
|
if(attr == Attrib_GLdouble) m_Real.glVertexAttrib1dv(idx, (GLdouble*)value);
|
|
else if(attr == Attrib_GLfloat) m_Real.glVertexAttrib1fv(idx, (GLfloat*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttrib1sv(idx, (GLshort*)value);
|
|
}
|
|
else if(Count == 2)
|
|
{
|
|
if(attr == Attrib_GLdouble) m_Real.glVertexAttrib2dv(idx, (GLdouble*)value);
|
|
else if(attr == Attrib_GLfloat) m_Real.glVertexAttrib2fv(idx, (GLfloat*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttrib2sv(idx, (GLshort*)value);
|
|
}
|
|
else if(Count == 3)
|
|
{
|
|
if(attr == Attrib_GLdouble) m_Real.glVertexAttrib3dv(idx, (GLdouble*)value);
|
|
else if(attr == Attrib_GLfloat) m_Real.glVertexAttrib3fv(idx, (GLfloat*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttrib3sv(idx, (GLshort*)value);
|
|
}
|
|
else
|
|
{
|
|
if(attr == Attrib_GLdouble) m_Real.glVertexAttrib4dv(idx, (GLdouble*)value);
|
|
else if(attr == Attrib_GLfloat) m_Real.glVertexAttrib4fv(idx, (GLfloat*)value);
|
|
else if(attr == Attrib_GLbyte) m_Real.glVertexAttrib4bv(idx, (GLbyte*)value);
|
|
else if(attr == Attrib_GLint) m_Real.glVertexAttrib4iv(idx, (GLint*)value);
|
|
else if(attr == Attrib_GLshort) m_Real.glVertexAttrib4sv(idx, (GLshort*)value);
|
|
else if(attr == Attrib_GLubyte) m_Real.glVertexAttrib4ubv(idx, (GLubyte*)value);
|
|
else if(attr == Attrib_GLuint) m_Real.glVertexAttrib4uiv(idx, (GLuint*)value);
|
|
else if(attr == Attrib_GLushort) m_Real.glVertexAttrib4usv(idx, (GLushort*)value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define ATTRIB_FUNC(count, suffix, TypeOr, paramtype, ...) \
|
|
void WrappedOpenGL::CONCAT(glVertexAttrib, suffix)(GLuint index, __VA_ARGS__) \
|
|
{ \
|
|
m_Real.CONCAT(glVertexAttrib, suffix)(index, ARRAYLIST); \
|
|
\
|
|
if(m_State >= WRITING_CAPFRAME) \
|
|
{ \
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIB_GENERIC); \
|
|
const paramtype vals[] = { ARRAYLIST }; \
|
|
Serialise_glVertexAttrib(index, count, eGL_NONE, GL_FALSE, vals, TypeOr | CONCAT(Attrib_, paramtype)); \
|
|
\
|
|
m_ContextRecord->AddChunk(scope.Get()); \
|
|
} \
|
|
}
|
|
|
|
#define ARRAYLIST x
|
|
|
|
ATTRIB_FUNC(1, 1f, 0, GLfloat, GLfloat x)
|
|
ATTRIB_FUNC(1, 1s, 0, GLshort, GLshort x)
|
|
ATTRIB_FUNC(1, 1d, 0, GLdouble, GLdouble x)
|
|
ATTRIB_FUNC(1, L1d, Attrib_L, GLdouble, GLdouble x)
|
|
ATTRIB_FUNC(1, I1i, Attrib_I, GLint, GLint x)
|
|
ATTRIB_FUNC(1, I1ui, Attrib_I, GLuint, GLuint x)
|
|
|
|
#undef ARRAYLIST
|
|
#define ARRAYLIST x, y
|
|
|
|
ATTRIB_FUNC(2, 2f, 0, GLfloat, GLfloat x, GLfloat y)
|
|
ATTRIB_FUNC(2, 2s, 0, GLshort, GLshort x, GLshort y)
|
|
ATTRIB_FUNC(2, 2d, 0, GLdouble, GLdouble x, GLdouble y)
|
|
ATTRIB_FUNC(2, L2d, Attrib_L, GLdouble, GLdouble x, GLdouble y)
|
|
ATTRIB_FUNC(2, I2i, Attrib_I, GLint, GLint x, GLint y)
|
|
ATTRIB_FUNC(2, I2ui, Attrib_I, GLuint, GLuint x, GLuint y)
|
|
|
|
#undef ARRAYLIST
|
|
#define ARRAYLIST x, y, z
|
|
|
|
ATTRIB_FUNC(3, 3f, 0, GLfloat, GLfloat x, GLfloat y, GLfloat z)
|
|
ATTRIB_FUNC(3, 3s, 0, GLshort, GLshort x, GLshort y, GLshort z)
|
|
ATTRIB_FUNC(3, 3d, 0, GLdouble, GLdouble x, GLdouble y, GLdouble z)
|
|
ATTRIB_FUNC(3, L3d, Attrib_L, GLdouble, GLdouble x, GLdouble y, GLdouble z)
|
|
ATTRIB_FUNC(3, I3i, Attrib_I, GLint, GLint x, GLint y, GLint z)
|
|
ATTRIB_FUNC(3, I3ui, Attrib_I, GLuint, GLuint x, GLuint y, GLuint z)
|
|
|
|
#undef ARRAYLIST
|
|
#define ARRAYLIST x, y, z, w
|
|
|
|
ATTRIB_FUNC(4, 4f, 0, GLfloat, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
|
|
ATTRIB_FUNC(4, 4s, 0, GLshort, GLshort x, GLshort y, GLshort z, GLshort w)
|
|
ATTRIB_FUNC(4, 4d, 0, GLdouble, GLdouble x, GLdouble y, GLdouble z, GLdouble w)
|
|
ATTRIB_FUNC(4, L4d, Attrib_L, GLdouble, GLdouble x, GLdouble y, GLdouble z, GLdouble w)
|
|
ATTRIB_FUNC(4, I4i, Attrib_I, GLint, GLint x, GLint y, GLint z, GLint w)
|
|
ATTRIB_FUNC(4, I4ui, Attrib_I, GLuint, GLuint x, GLuint y, GLuint z, GLuint w)
|
|
ATTRIB_FUNC(4, 4Nub, Attrib_N, GLubyte, GLubyte x, GLubyte y, GLubyte z, GLubyte w)
|
|
|
|
#undef ATTRIB_FUNC
|
|
#define ATTRIB_FUNC(count, suffix, TypeOr, paramtype) \
|
|
void WrappedOpenGL::CONCAT(glVertexAttrib, suffix)(GLuint index, const paramtype *value) \
|
|
{ \
|
|
m_Real.CONCAT(glVertexAttrib, suffix)(index, value); \
|
|
\
|
|
if(m_State >= WRITING_CAPFRAME) \
|
|
{ \
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIB_GENERIC); \
|
|
Serialise_glVertexAttrib(index, count, eGL_NONE, GL_FALSE, value, TypeOr | CONCAT(Attrib_, paramtype)); \
|
|
\
|
|
m_ContextRecord->AddChunk(scope.Get()); \
|
|
} \
|
|
}
|
|
|
|
ATTRIB_FUNC(1, 1dv, 0, GLdouble)
|
|
ATTRIB_FUNC(2, 2dv, 0, GLdouble)
|
|
ATTRIB_FUNC(3, 3dv, 0, GLdouble)
|
|
ATTRIB_FUNC(4, 4dv, 0, GLdouble)
|
|
ATTRIB_FUNC(1, 1sv, 0, GLshort)
|
|
ATTRIB_FUNC(2, 2sv, 0, GLshort)
|
|
ATTRIB_FUNC(3, 3sv, 0, GLshort)
|
|
ATTRIB_FUNC(4, 4sv, 0, GLshort)
|
|
ATTRIB_FUNC(1, 1fv, 0, GLfloat)
|
|
ATTRIB_FUNC(2, 2fv, 0, GLfloat)
|
|
ATTRIB_FUNC(3, 3fv, 0, GLfloat)
|
|
ATTRIB_FUNC(4, 4fv, 0, GLfloat)
|
|
ATTRIB_FUNC(4, 4bv, 0, GLbyte)
|
|
ATTRIB_FUNC(4, 4iv, 0, GLint)
|
|
ATTRIB_FUNC(4, 4uiv, 0, GLuint)
|
|
ATTRIB_FUNC(4, 4usv, 0, GLushort)
|
|
ATTRIB_FUNC(4, 4ubv, 0, GLubyte)
|
|
|
|
ATTRIB_FUNC(1, L1dv, Attrib_L, GLdouble)
|
|
ATTRIB_FUNC(2, L2dv, Attrib_L, GLdouble)
|
|
ATTRIB_FUNC(3, L3dv, Attrib_L, GLdouble)
|
|
ATTRIB_FUNC(4, L4dv, Attrib_L, GLdouble)
|
|
|
|
ATTRIB_FUNC(1, I1iv, Attrib_I, GLint)
|
|
ATTRIB_FUNC(1, I1uiv, Attrib_I, GLuint)
|
|
ATTRIB_FUNC(2, I2iv, Attrib_I, GLint)
|
|
ATTRIB_FUNC(2, I2uiv, Attrib_I, GLuint)
|
|
ATTRIB_FUNC(3, I3iv, Attrib_I, GLint)
|
|
ATTRIB_FUNC(3, I3uiv, Attrib_I, GLuint)
|
|
|
|
ATTRIB_FUNC(4, I4bv, Attrib_I, GLbyte)
|
|
ATTRIB_FUNC(4, I4iv, Attrib_I, GLint)
|
|
ATTRIB_FUNC(4, I4sv, Attrib_I, GLshort)
|
|
ATTRIB_FUNC(4, I4ubv, Attrib_I, GLubyte)
|
|
ATTRIB_FUNC(4, I4uiv, Attrib_I, GLuint)
|
|
ATTRIB_FUNC(4, I4usv, Attrib_I, GLushort)
|
|
|
|
ATTRIB_FUNC(4, 4Nbv, Attrib_N, GLbyte)
|
|
ATTRIB_FUNC(4, 4Niv, Attrib_N, GLint)
|
|
ATTRIB_FUNC(4, 4Nsv, Attrib_N, GLshort)
|
|
ATTRIB_FUNC(4, 4Nubv, Attrib_N, GLubyte)
|
|
ATTRIB_FUNC(4, 4Nuiv, Attrib_N, GLuint)
|
|
ATTRIB_FUNC(4, 4Nusv, Attrib_N, GLushort)
|
|
|
|
#undef ATTRIB_FUNC
|
|
#define ATTRIB_FUNC(count, suffix, funcparam, passparam) \
|
|
void WrappedOpenGL::CONCAT(CONCAT(glVertexAttribP, count), suffix)(GLuint index, GLenum type, GLboolean normalized, funcparam) \
|
|
{ \
|
|
m_Real.CONCAT(CONCAT(glVertexAttribP, count), suffix)(index, type, normalized, value); \
|
|
\
|
|
if(m_State >= WRITING_CAPFRAME) \
|
|
{ \
|
|
SCOPED_SERIALISE_CONTEXT(VERTEXATTRIB_GENERIC); \
|
|
Serialise_glVertexAttrib(index, count, type, normalized, passparam, Attrib_packed); \
|
|
\
|
|
m_ContextRecord->AddChunk(scope.Get()); \
|
|
} \
|
|
}
|
|
|
|
ATTRIB_FUNC(1, ui, GLuint value, &value)
|
|
ATTRIB_FUNC(2, ui, GLuint value, &value)
|
|
ATTRIB_FUNC(3, ui, GLuint value, &value)
|
|
ATTRIB_FUNC(4, ui, GLuint value, &value)
|
|
ATTRIB_FUNC(1, uiv, const GLuint *value, value)
|
|
ATTRIB_FUNC(2, uiv, const GLuint *value, value)
|
|
ATTRIB_FUNC(3, uiv, const GLuint *value, value)
|
|
ATTRIB_FUNC(4, uiv, const GLuint *value, value)
|
|
|
|
#pragma endregion |