2016-09-14 18:01:13 +00:00
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2014-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
2014-07-30 22:44:22 +00:00
/*
* * gl_lightbuffer . cpp
* * Buffer data maintenance for dynamic lights
* *
2016-09-14 18:01:13 +00:00
* */
2014-07-30 22:44:22 +00:00
# include "gl/system/gl_system.h"
2014-08-01 18:59:39 +00:00
# include "gl/shaders/gl_shader.h"
2014-07-30 22:44:22 +00:00
# include "gl/dynlights/gl_lightbuffer.h"
# include "gl/dynlights/gl_dynlight.h"
# include "gl/system/gl_interface.h"
2014-08-01 18:59:39 +00:00
# include "gl/utility//gl_clock.h"
2014-07-30 22:44:22 +00:00
2014-08-19 12:18:21 +00:00
static const int INITIAL_BUFFER_SIZE = 160000 ; // This means 80000 lights per frame and 160000*16 bytes == 2.56 MB.
float * mMap ;
2014-07-30 22:44:22 +00:00
FLightBuffer : : FLightBuffer ( )
{
2014-08-19 12:18:21 +00:00
mBufferSize = INITIAL_BUFFER_SIZE ;
mByteSize = mBufferSize * sizeof ( float ) ;
2014-09-14 19:29:13 +00:00
if ( gl . flags & RFL_SHADER_STORAGE_BUFFER )
2014-07-30 22:44:22 +00:00
{
mBufferType = GL_SHADER_STORAGE_BUFFER ;
2014-09-17 09:03:05 +00:00
mBlockAlign = 0 ;
2014-08-19 12:18:21 +00:00
mBlockSize = mBufferSize ;
2014-07-30 22:44:22 +00:00
}
else
{
mBufferType = GL_UNIFORM_BUFFER ;
2014-08-02 18:41:13 +00:00
mBlockSize = gl . maxuniformblock / 16 ;
if ( mBlockSize > 2048 ) mBlockSize = 2048 ; // we don't really need a larger buffer
mBlockAlign = mBlockSize / 2 ;
2014-07-30 22:44:22 +00:00
}
2014-08-01 18:59:39 +00:00
glGenBuffers ( 1 , & mBufferId ) ;
- decided to restrict the 2.0 beta to OpenGL 4.x with GL_ARB_buffer_storage extension and removed all code for supporting older versions.
Sadly, anything else makes no sense.
All the recently made changes live or die, depending on this extension's presence.
Without it, there are major performance issues with the buffer uploads. All of the traditional buffer upload methods are without exception horrendously slow, especially in the context of a Doom engine where frequent small updates are required.
It could be solved with a complete restructuring of the engine, of course, but that's hardly worth the effort, considering it's only for legacy hardware whose market share will inevitably shrink considerably over the next years.
And even then, under the best circumstances I'd still get the same performance as the old immediate mode renderer in GZDoom 1.x and still couldn't implement the additions I'd like to make.
So, since I need to keep GZDoom 1.x around anyway for older GL 2.x hardware, it may as well serve for 3.x hardware, too. It's certainly less work than constantly trying to find workarounds for the older hardware's limitations that cost more time than working on future-proofing the engine.
This new, trimmed down 4.x renderer runs on a core profile configuration and uses persistently mapped buffers for nearly everything that is getting transferred to the GPU. (The global uniforms are still being used as such but they'll be phased out after the first beta release.
2014-08-01 20:42:39 +00:00
glBindBufferBase ( mBufferType , LIGHTBUF_BINDINGPOINT , mBufferId ) ;
2015-01-24 12:13:54 +00:00
glBindBuffer ( mBufferType , mBufferId ) ; // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should.
2016-08-04 10:55:21 +00:00
if ( gl . lightmethod = = LM_DIRECT )
2014-08-19 12:18:21 +00:00
{
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 ;
}
2014-08-01 18:59:39 +00:00
Clear ( ) ;
mLastMappedIndex = UINT_MAX ;
}
FLightBuffer : : ~ FLightBuffer ( )
{
glBindBuffer ( mBufferType , 0 ) ;
glDeleteBuffers ( 1 , & mBufferId ) ;
2014-07-30 22:44:22 +00:00
}
void FLightBuffer : : Clear ( )
{
mIndex = 0 ;
2014-08-19 12:18:21 +00:00
mIndices . Clear ( ) ;
mUploadIndex = 0 ;
2014-07-30 22:44:22 +00:00
}
2014-08-01 18:59:39 +00:00
int FLightBuffer : : UploadLights ( FDynLightData & data )
2014-07-30 22:44:22 +00:00
{
2014-09-20 07:04:36 +00:00
int size0 = data . arrays [ 0 ] . Size ( ) / 4 ;
int size1 = data . arrays [ 1 ] . Size ( ) / 4 ;
int size2 = data . arrays [ 2 ] . Size ( ) / 4 ;
int totalsize = size0 + size1 + size2 + 1 ;
2014-07-30 22:44:22 +00:00
2014-09-20 07:04:36 +00:00
// pointless type casting because some compilers can't print enough warnings.
if ( mBlockAlign > 0 & & ( unsigned int ) totalsize + ( mIndex % mBlockAlign ) > mBlockSize )
2014-08-02 18:41:13 +00:00
{
mIndex = ( ( mIndex + mBlockAlign ) / mBlockAlign ) * mBlockAlign ;
// can't be rendered all at once.
2014-09-20 07:04:36 +00:00
if ( ( unsigned int ) totalsize > mBlockSize )
2014-08-02 18:41:13 +00:00
{
2014-09-20 07:04:36 +00:00
int diff = totalsize - ( int ) mBlockSize ;
2014-08-02 18:41:13 +00:00
size2 - = diff ;
if ( size2 < 0 )
{
size1 + = size2 ;
size2 = 0 ;
}
if ( size1 < 0 )
{
size0 + = size1 ;
size1 = 0 ;
}
totalsize = size0 + size1 + size2 + 1 ;
}
}
2014-08-01 18:59:39 +00:00
if ( totalsize < = 1 ) return - 1 ;
2014-07-30 22:44:22 +00:00
2014-10-23 10:06:00 +00:00
if ( mIndex + totalsize > mBufferSize / 4 )
2014-07-30 22:44:22 +00:00
{
2014-08-19 12:18:21 +00:00
// 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 ) ;
2015-01-24 12:13:54 +00:00
glBindBuffer ( mBufferType , newbuffer ) ; // Note: Some older AMD drivers don't do that in glBindBufferBase, as they should.
2014-08-19 12:18:21 +00:00
glBindBuffer ( GL_COPY_READ_BUFFER , mBufferId ) ;
// create the new buffer's storage (twice as large as the old one)
mBufferSize * = 2 ;
mByteSize * = 2 ;
2016-08-04 10:55:21 +00:00
if ( gl . lightmethod = = LM_DIRECT )
2014-08-19 12:18:21 +00:00
{
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 ) ;
}
// 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 ;
2014-07-30 22:44:22 +00:00
}
float * copyptr ;
2014-08-19 12:18:21 +00:00
assert ( mBufferPointer ! = NULL ) ;
if ( mBufferPointer = = NULL ) return - 1 ;
- decided to restrict the 2.0 beta to OpenGL 4.x with GL_ARB_buffer_storage extension and removed all code for supporting older versions.
Sadly, anything else makes no sense.
All the recently made changes live or die, depending on this extension's presence.
Without it, there are major performance issues with the buffer uploads. All of the traditional buffer upload methods are without exception horrendously slow, especially in the context of a Doom engine where frequent small updates are required.
It could be solved with a complete restructuring of the engine, of course, but that's hardly worth the effort, considering it's only for legacy hardware whose market share will inevitably shrink considerably over the next years.
And even then, under the best circumstances I'd still get the same performance as the old immediate mode renderer in GZDoom 1.x and still couldn't implement the additions I'd like to make.
So, since I need to keep GZDoom 1.x around anyway for older GL 2.x hardware, it may as well serve for 3.x hardware, too. It's certainly less work than constantly trying to find workarounds for the older hardware's limitations that cost more time than working on future-proofing the engine.
This new, trimmed down 4.x renderer runs on a core profile configuration and uses persistently mapped buffers for nearly everything that is getting transferred to the GPU. (The global uniforms are still being used as such but they'll be phased out after the first beta release.
2014-08-01 20:42:39 +00:00
copyptr = mBufferPointer + mIndex * 4 ;
2014-07-30 22:44:22 +00:00
2014-09-17 09:03:05 +00:00
float parmcnt [ ] = { 0 , float ( size0 ) , float ( size0 + size1 ) , float ( size0 + size1 + size2 ) } ;
2014-07-30 22:44:22 +00:00
memcpy ( & copyptr [ 0 ] , parmcnt , 4 * sizeof ( float ) ) ;
2014-08-01 18:59:39 +00:00
memcpy ( & copyptr [ 4 ] , & data . arrays [ 0 ] [ 0 ] , 4 * size0 * sizeof ( float ) ) ;
memcpy ( & copyptr [ 4 + 4 * size0 ] , & data . arrays [ 1 ] [ 0 ] , 4 * size1 * sizeof ( float ) ) ;
memcpy ( & copyptr [ 4 + 4 * ( size0 + size1 ) ] , & data . arrays [ 2 ] [ 0 ] , 4 * size2 * sizeof ( float ) ) ;
unsigned int bufferindex = mIndex ;
2014-07-30 22:44:22 +00:00
mIndex + = totalsize ;
2014-08-01 18:59:39 +00:00
draw_dlight + = ( totalsize - 1 ) / 2 ;
return bufferindex ;
2014-07-30 22:44:22 +00:00
}
2014-08-19 12:18:21 +00:00
void FLightBuffer : : Begin ( )
{
2016-08-04 10:55:21 +00:00
if ( gl . lightmethod = = LM_DEFERRED )
2014-08-19 12:18:21 +00:00
{
glBindBuffer ( mBufferType , mBufferId ) ;
2014-09-15 08:27:09 +00:00
mBufferPointer = ( float * ) glMapBufferRange ( mBufferType , 0 , mByteSize , GL_MAP_WRITE_BIT ) ;
2014-08-19 12:18:21 +00:00
}
}
2014-07-30 22:44:22 +00:00
void FLightBuffer : : Finish ( )
{
2016-08-04 10:55:21 +00:00
if ( gl . lightmethod = = LM_DEFERRED )
2014-08-19 12:18:21 +00:00
{
glBindBuffer ( mBufferType , mBufferId ) ;
glUnmapBuffer ( mBufferType ) ;
mBufferPointer = NULL ;
}
2014-08-01 18:59:39 +00:00
}
int FLightBuffer : : BindUBO ( unsigned int index )
{
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 * 16 , mBlockSize * 16 ) ; // we go from counting vec4's to counting bytes here.
}
return ( index - offset ) ;
2014-07-30 22:44:22 +00:00
}