<commit_before>/******************************************************************************
 * 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);
		}
	}
}

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)
	{
		size_t idx = BufferIdx(target);

		if(buffer != 0)
			GetCtxData().m_BufferRecord[idx]->datatype = Target;
	}
	else if(m_State < WRITING)
	{
		if(Id == ResourceId())
		{
			m_Real.glBindBuffer(Target, 0);
		}
		else
		{
			GLResource res = GetResourceManager()->GetLiveResource(Id);
			m_Real.glBindBuffer(Target, res.name);

			m_Buffers[GetResourceManager()->GetLiveID(Id)].curType = Target;
		}
	}

	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();
		}
		
		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;

			{
				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());
		}
	}
}

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);

	byte *dummy = NULL;

	if(m_State >= WRITING && data == NULL)
	{
		dummy = new byte[size];
		data = dummy;
	}

	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);
	}

	if(dummy)
		delete[] dummy;

	return true;
}

void WrappedOpenGL::glNamedBufferStorageEXT(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags)
{
	m_Real.glNamedBufferStorageEXT(buffer, size, data, flags);
	
	if(m_State >= WRITING)
	{
		GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
		RDCASSERT(record);

		SCOPED_SERIALISE_CONTEXT(BUFFERSTORAGE);
		Serialise_glNamedBufferStorageEXT(buffer, size, data, flags);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
		}
	}
}

void WrappedOpenGL::glBufferStorage(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags)
{
	m_Real.glBufferStorage(target, size, data, flags);
	
	size_t idx = BufferIdx(target);

	if(m_State >= WRITING)
	{
		GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
		RDCASSERT(record);

		SCOPED_SERIALISE_CONTEXT(BUFFERSTORAGE);
		Serialise_glNamedBufferStorageEXT(record->Resource.name,
																   size, data, flags);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
		}
	}
}

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);

	byte *dummy = NULL;

	if(m_State >= WRITING && data == NULL)
	{
		dummy = new byte[size];
		data = dummy;
	}

	SERIALISE_ELEMENT_BUF(byte *, bytes, data, (size_t)Bytesize);

	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);
	}

	if(dummy)
		delete[] dummy;

	return true;
}

void WrappedOpenGL::glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage)
{
	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);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
			record->usage = usage;
		}
	}
}

void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
{
	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);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
			record->usage = usage;
		}
	}
}

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 > 60)
			{
				m_HighTrafficResources.insert(record->GetResourceID());
				GetResourceManager()->MarkDirtyResource(record->GetResourceID());
			}
		}
	}
}

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 > 60)
			{
				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)
{
	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::glCopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
{
	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;
	
		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);

		if(buffer == 0)
			cd.m_BufferRecord[idx] = NULL;
		else
			cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
	}

	// 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);

		if(buffer == 0)
			cd.m_BufferRecord[idx] = NULL;
		else
			cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
	}

	// 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 && first == 0 && 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]));
	}

	// 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 && first == 0 && 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]));
	}

	// 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);<commit_msg>Mark buffers referenced so they're kept alive even if deleted in-frame<commit_after>/******************************************************************************
 * 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);
		}
	}
}

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)
	{
		size_t idx = BufferIdx(target);

		if(buffer != 0)
			GetCtxData().m_BufferRecord[idx]->datatype = Target;
	}
	else if(m_State < WRITING)
	{
		if(Id == ResourceId())
		{
			m_Real.glBindBuffer(Target, 0);
		}
		else
		{
			GLResource res = GetResourceManager()->GetLiveResource(Id);
			m_Real.glBindBuffer(Target, res.name);

			m_Buffers[GetResourceManager()->GetLiveID(Id)].curType = Target;
		}
	}

	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;

			{
				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());
		}
	}
}

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);

	byte *dummy = NULL;

	if(m_State >= WRITING && data == NULL)
	{
		dummy = new byte[size];
		data = dummy;
	}

	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);
	}

	if(dummy)
		delete[] dummy;

	return true;
}

void WrappedOpenGL::glNamedBufferStorageEXT(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags)
{
	m_Real.glNamedBufferStorageEXT(buffer, size, data, flags);
	
	if(m_State >= WRITING)
	{
		GLResourceRecord *record = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
		RDCASSERT(record);

		SCOPED_SERIALISE_CONTEXT(BUFFERSTORAGE);
		Serialise_glNamedBufferStorageEXT(buffer, size, data, flags);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
		}
	}
}

void WrappedOpenGL::glBufferStorage(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags)
{
	m_Real.glBufferStorage(target, size, data, flags);
	
	size_t idx = BufferIdx(target);

	if(m_State >= WRITING)
	{
		GLResourceRecord *record = GetCtxData().m_BufferRecord[BufferIdx(target)];
		RDCASSERT(record);

		SCOPED_SERIALISE_CONTEXT(BUFFERSTORAGE);
		Serialise_glNamedBufferStorageEXT(record->Resource.name,
																   size, data, flags);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
		}
	}
}

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);

	byte *dummy = NULL;

	if(m_State >= WRITING && data == NULL)
	{
		dummy = new byte[size];
		data = dummy;
	}

	SERIALISE_ELEMENT_BUF(byte *, bytes, data, (size_t)Bytesize);

	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);
	}

	if(dummy)
		delete[] dummy;

	return true;
}

void WrappedOpenGL::glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage)
{
	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);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
			record->usage = usage;
		}
	}
}

void WrappedOpenGL::glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
{
	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);

		Chunk *chunk = scope.Get();

		if(m_State == WRITING_CAPFRAME)
		{
			m_ContextRecord->AddChunk(chunk);
		}
		else
		{
			record->AddChunk(chunk);
			record->SetDataPtr(chunk->GetData());
			record->Length = (int32_t)size;
			record->usage = usage;
		}
	}
}

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 > 60)
			{
				m_HighTrafficResources.insert(record->GetResourceID());
				GetResourceManager()->MarkDirtyResource(record->GetResourceID());
			}
		}
	}
}

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 > 60)
			{
				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)
{
	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::glCopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
{
	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;
	
		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);

		if(buffer == 0)
			cd.m_BufferRecord[idx] = NULL;
		else
			cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
		
		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);
		}
	}

	// 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);

		if(buffer == 0)
			cd.m_BufferRecord[idx] = NULL;
		else
			cd.m_BufferRecord[idx] = GetResourceManager()->GetResourceRecord(BufferRes(GetCtx(), buffer));
		
		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);
		}
	}

	// 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 && first == 0 && 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]));
		
		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);
	}

	// 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 && first == 0 && 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]));
		
		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);
	}

	// 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);
{'fraction_non_alphanumeric': 0.1275866725179759, 'fraction_numerical': 0.004759815008608176, 'mean_word_length': 4.878261732314714, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 413, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4732909', 'n_tokens_mistral': 22751, 'n_tokens_neox': 20233, 'n_words': 4055}