diff --git a/src/gl/dynlights/gl_lightbuffer.cpp b/src/gl/dynlights/gl_lightbuffer.cpp index 0c98d601e8..adcc881e36 100644 --- a/src/gl/dynlights/gl_lightbuffer.cpp +++ b/src/gl/dynlights/gl_lightbuffer.cpp @@ -25,8 +25,6 @@ ** **/ -#include "gl_load/gl_system.h" -#include "gl/shaders/gl_shader.h" #include "gl/dynlights/gl_lightbuffer.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" @@ -40,42 +38,30 @@ FLightBuffer::FLightBuffer() { int maxNumberOfLights = 40000; - mPersistentBuffer = screen->BuffersArePersistent(); mBufferSize = maxNumberOfLights * ELEMENTS_PER_LIGHT; mByteSize = mBufferSize * ELEMENT_SIZE; // Hack alert: On Intel's GL driver SSBO's perform quite worse than UBOs. // We only want to disable using SSBOs for lights but not disable the feature entirely. // Note that using an uniform buffer here will limit the number of lights per surface so it isn't done for NVidia and AMD. - if (gl.flags & RFL_SHADER_STORAGE_BUFFER && !strstr(gl.vendorstring, "Intel")) + if (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER && !strstr(screen->gl_vendorstring, "Intel")) { - mBufferType = GL_SHADER_STORAGE_BUFFER; + mBufferType = true; mBlockAlign = 0; mBlockSize = mBufferSize; mMaxUploadSize = mBlockSize; } else { - mBufferType = GL_UNIFORM_BUFFER; - mBlockSize = gl.maxuniformblock / ELEMENT_SIZE; - mBlockAlign = gl.uniformblockalignment / ELEMENT_SIZE; + mBufferType = false; + mBlockSize = screen->maxuniformblock / ELEMENT_SIZE; + mBlockAlign = screen->uniformblockalignment / ELEMENT_SIZE; mMaxUploadSize = (mBlockSize - mBlockAlign); - mByteSize += gl.maxuniformblock; // to avoid mapping beyond the end of the buffer. + mByteSize += screen->maxuniformblock; // to avoid mapping beyond the end of the buffer. } - glGenBuffers(1, &mBufferId); - glBindBufferBase(mBufferType, LIGHTBUF_BINDINGPOINT, mBufferId); - glBindBuffer(mBufferType, mBufferId); // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should. - if (mPersistentBuffer) - { - glBufferStorage(mBufferType, mByteSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - } - else - { - glBufferData(mBufferType, mByteSize, NULL, GL_DYNAMIC_DRAW); - mBufferPointer = NULL; - } + mBuffer = screen->CreateDataBuffer(LIGHTBUF_BINDINGPOINT, mBufferType); + mBuffer->SetData(mByteSize, nullptr, false); Clear(); mLastMappedIndex = UINT_MAX; @@ -83,8 +69,7 @@ FLightBuffer::FLightBuffer() FLightBuffer::~FLightBuffer() { - glBindBuffer(mBufferType, 0); - glDeleteBuffers(1, &mBufferId); + delete mBuffer; } void FLightBuffer::Clear() @@ -94,20 +79,7 @@ void FLightBuffer::Clear() void FLightBuffer::CheckSize() { - // reallocate the buffer with twice the size - unsigned int newbuffer; - - // first unmap the old buffer - glBindBuffer(mBufferType, mBufferId); - glUnmapBuffer(mBufferType); - - // create and bind the new buffer, bind the old one to a copy target (too bad that DSA is not yet supported well enough to omit this crap.) - glGenBuffers(1, &newbuffer); - glBindBufferBase(mBufferType, LIGHTBUF_BINDINGPOINT, newbuffer); - glBindBuffer(mBufferType, newbuffer); // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should. - glBindBuffer(GL_COPY_READ_BUFFER, mBufferId); - - // create the new buffer's storage (twice as large as the old one) + // create the new buffer's storage (at least twice as large as the old one) int oldbytesize = mByteSize; unsigned int bufferbytesize = mBufferedData.Size() * 4; if (bufferbytesize > mByteSize) @@ -119,27 +91,12 @@ void FLightBuffer::CheckSize() mByteSize *= 2; } mBufferSize = mByteSize / ELEMENT_SIZE; - - if (mPersistentBuffer) - { - glBufferStorage(mBufferType, mByteSize, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - } - else - { - glBufferData(mBufferType, mByteSize, NULL, GL_DYNAMIC_DRAW); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT); - } + mBuffer->Resize(mByteSize); - // copy contents and delete the old buffer. - glCopyBufferSubData(GL_COPY_READ_BUFFER, mBufferType, 0, 0, mByteSize/2); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - glDeleteBuffers(1, &mBufferId); - mBufferId = newbuffer; - Begin(); - memcpy(mBufferPointer + mBlockSize*4, &mBufferedData[0], bufferbytesize); + Map(); + memcpy(((float*)mBuffer->Memory()) + mBlockSize*4, &mBufferedData[0], bufferbytesize); mBufferedData.Clear(); - Finish(); + Unmap(); } int FLightBuffer::UploadLights(FDynLightData &data) @@ -168,6 +125,7 @@ int FLightBuffer::UploadLights(FDynLightData &data) totalsize = size0 + size1 + size2 + 1; } + float *mBufferPointer = (float*)mBuffer->Memory(); assert(mBufferPointer != nullptr); if (mBufferPointer == nullptr) return -1; if (totalsize <= 1) return -1; // there are no lights @@ -200,35 +158,15 @@ int FLightBuffer::UploadLights(FDynLightData &data) } } -void FLightBuffer::Begin() -{ - if (!mPersistentBuffer) - { - glBindBuffer(mBufferType, mBufferId); - mBufferPointer = (float*)glMapBufferRange(mBufferType, 0, mByteSize, GL_MAP_WRITE_BIT); - } -} - -void FLightBuffer::Finish() -{ - if (!mPersistentBuffer) - { - glBindBuffer(mBufferType, mBufferId); - glUnmapBuffer(mBufferType); - mBufferPointer = NULL; - } - if (mBufferedData.Size() > 0) CheckSize(); -} - -int FLightBuffer::BindUBO(unsigned int index) +int FLightBuffer::DoBindUBO(unsigned int index) { + // this function will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start. unsigned int offset = (index / mBlockAlign) * mBlockAlign; if (offset != mLastMappedIndex) { - // this will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start to all shader programs mLastMappedIndex = offset; - glBindBufferRange(GL_UNIFORM_BUFFER, LIGHTBUF_BINDINGPOINT, mBufferId, offset * ELEMENT_SIZE, mBlockSize * ELEMENT_SIZE); + mBuffer->BindRange(offset * ELEMENT_SIZE, mBlockSize * ELEMENT_SIZE); } return (index - offset); } diff --git a/src/gl/dynlights/gl_lightbuffer.h b/src/gl/dynlights/gl_lightbuffer.h index 2a5cedbf03..9cdcbe5a1d 100644 --- a/src/gl/dynlights/gl_lightbuffer.h +++ b/src/gl/dynlights/gl_lightbuffer.h @@ -3,15 +3,17 @@ #include "tarray.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" +#include "hwrenderer/data/buffers.h" #include #include +class FRenderState; + class FLightBuffer { - unsigned int mBufferId; - float * mBufferPointer; + IDataBuffer *mBuffer; - unsigned int mBufferType; + bool mBufferType; std::atomic mIndex; unsigned int mLastMappedIndex; unsigned int mBlockAlign; @@ -19,24 +21,25 @@ class FLightBuffer unsigned int mBufferSize; unsigned int mByteSize; unsigned int mMaxUploadSize; - bool mPersistentBuffer; std::mutex mBufferMutex; TArray mBufferedData; + void CheckSize(); + public: FLightBuffer(); ~FLightBuffer(); void Clear(); int UploadLights(FDynLightData &data); - void Begin(); - void Finish(); - void CheckSize(); - int BindUBO(unsigned int index); + void Map() { mBuffer->Map(); } + void Unmap() { mBuffer->Unmap(); if (mBufferedData.Size() > 0) CheckSize(); } unsigned int GetBlockSize() const { return mBlockSize; } - unsigned int GetBufferType() const { return mBufferType; } - + bool GetBufferType() const { return mBufferType; } + + int DoBindUBO(unsigned int index); + int ShaderIndex(unsigned int index) const { if (mBlockAlign == 0) return index; @@ -45,6 +48,22 @@ public: return int(index-offset); } + // Only relevant for OpenGL, so this does not need access to the render state. + int BindUBO(unsigned int index) + { + if (!mBufferType && index > -1) + { + index = DoBindUBO(index); + } + return index; + } + + // The parameter is a reminder for Vulkan. + void BindBase(FRenderState &state) + { + mBuffer->BindBase(); + } + }; int gl_SetDynModelLight(AActor *self, int dynlightindex); diff --git a/src/gl/renderer/gl_renderstate.cpp b/src/gl/renderer/gl_renderstate.cpp index 09af0c067e..9df29b341a 100644 --- a/src/gl/renderer/gl_renderstate.cpp +++ b/src/gl/renderer/gl_renderstate.cpp @@ -200,11 +200,7 @@ bool FGLRenderState::ApplyShader() matrixToGL(identityMatrix, activeShader->normalmodelmatrix_index); } - auto index = mLightIndex; - if (index > -1 && GLRenderer->mLights->GetBufferType() == GL_UNIFORM_BUFFER) - { - index = GLRenderer->mLights->BindUBO(index); - } + auto index = GLRenderer->mLights->BindUBO(mLightIndex); activeShader->muLightIndex.Set(index); return true; diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 44c63fe2d6..4b2602f615 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -105,7 +105,7 @@ void FDrawInfo::CreateScene() // clip the scene and fill the drawlists Bsp.Clock(); screen->mVertexData->Map(); - GLRenderer->mLights->Begin(); + GLRenderer->mLights->Map(); // Give the DrawInfo the viewpoint in fixed point because that's what the nodes are. viewx = FLOAT2FIXED(vp.Pos.X); @@ -130,7 +130,7 @@ void FDrawInfo::CreateScene() HandleHackedSubsectors(); // open sector hacks for deep water ProcessSectorStacks(in_area); // merge visplanes of sector stacks PrepareUnhandledMissingTextures(); - GLRenderer->mLights->Finish(); + GLRenderer->mLights->Unmap(); screen->mVertexData->Unmap(); ProcessAll.Unclock(); @@ -153,6 +153,7 @@ void FDrawInfo::RenderScene(int recursion) glDepthMask(true); if (!gl_no_skyclear) screen->mPortalState->RenderFirstSkyPortal(recursion, this); + GLRenderer->mLights->BindBase(gl_RenderState); // not needed for OpenGL but necessary for Vulkan command buffers to do it here! gl_RenderState.EnableFog(true); gl_RenderState.SetRenderStyle(STYLE_Source); diff --git a/src/gl/shaders/gl_shader.cpp b/src/gl/shaders/gl_shader.cpp index 71e27b56fa..62e1a190f1 100644 --- a/src/gl/shaders/gl_shader.cpp +++ b/src/gl/shaders/gl_shader.cpp @@ -168,9 +168,9 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * assert(GLRenderer->mLights != NULL); - unsigned int lightbuffertype = GLRenderer->mLights->GetBufferType(); + bool lightbuffertype = GLRenderer->mLights->GetBufferType(); unsigned int lightbuffersize = GLRenderer->mLights->GetBlockSize(); - if (lightbuffertype == GL_UNIFORM_BUFFER) + if (!lightbuffertype) { vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n", lightbuffersize); } @@ -344,7 +344,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * texturematrix_index = glGetUniformLocation(hShader, "TextureMatrix"); normalmodelmatrix_index = glGetUniformLocation(hShader, "NormalModelMatrix"); - if (lightbuffertype == GL_UNIFORM_BUFFER) + if (!lightbuffertype) { int tempindex = glGetUniformBlockIndex(hShader, "LightBufferUBO"); if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, LIGHTBUF_BINDINGPOINT); diff --git a/src/gl/system/gl_framebuffer.cpp b/src/gl/system/gl_framebuffer.cpp index fdcf12b903..163c921aa9 100644 --- a/src/gl/system/gl_framebuffer.cpp +++ b/src/gl/system/gl_framebuffer.cpp @@ -119,6 +119,8 @@ void OpenGLFrameBuffer::InitializeState() hwcaps = gl.flags; glslversion = gl.glslversion; uniformblockalignment = gl.uniformblockalignment; + maxuniformblock = gl.maxuniformblock; + gl_vendorstring = gl.vendorstring; if (first) { diff --git a/src/v_video.h b/src/v_video.h index e285923888..0776b42ca4 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -374,6 +374,8 @@ public: int stencilValue = 0; // Global stencil test value bool enable_quadbuffered = false; // Quad-buffered stereo available? unsigned int uniformblockalignment = 256; // Hardware dependent uniform buffer alignment. + unsigned int maxuniformblock = 65536; + const char *gl_vendorstring; // On OpenGL (not Vulkan) we have to account for some issues with Intel. FPortalSceneState *mPortalState; // global portal state. FSkyVertexBuffer *mSkyData = nullptr; // the sky vertex buffer FFlatVertexBuffer *mVertexData = nullptr; // Global vertex data