Supermodel/Src/Graphics/New3D/R3DFrameBuffers.cpp
Ian Curtis 40c8259130 Rewrite the whole project for GL4+. I figured if we removed the limitation of a legacy rendering API we could improve things a bit. With GL4+ we can do unsigned integer math in the shaders. This allows us to upload a direct copy of the real3d texture sheet, and texture directly from this memory given the x/y pos and type. This massively simplifies the binding and invalidation code. Also the crazy corner cases will work because it essentially works the same way as the original hardware.
The standard triangle render requires gl 4.1 core, so should work on mac. The quad renderer runs on 4.5 core. The legacy renderer should still work, and when enabled a regular opengl context will be created, which allows functions marked depreciated in the core profiles to still work. This will only work in windows/linux I think. Apple doesn't support this.

A GL 4.1 GPU is now the min required spec. Sorry if you have an OLDER gpu. GL 4.1 is over 12 years old now.

This is a big update so I apologise in advance if I accidently broke something :]
2022-11-07 21:33:01 +00:00

473 lines
12 KiB
C++

#include "R3DFrameBuffers.h"
#include "Mat4.h"
#define countof(a) (sizeof(a)/sizeof(*(a)))
namespace New3D {
R3DFrameBuffers::R3DFrameBuffers()
{
m_frameBufferID = 0;
m_renderBufferID = 0;
m_frameBufferIDCopy = 0;
m_renderBufferIDCopy = 0;
m_width = 0;
m_height = 0;
m_vao = 0;
for (auto &i : m_texIDs) {
i = 0;
}
m_lastLayer = Layer::none;
AllocShaderTrans();
AllocShaderBase();
AllocShaderWipe();
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
// no states needed since we do it in the shader
glBindVertexArray(0);
}
R3DFrameBuffers::~R3DFrameBuffers()
{
DestroyFBO();
m_shaderTrans.UnloadShaders();
m_shaderBase.UnloadShaders();
m_shaderWipe.UnloadShaders();
if (m_vao) {
glDeleteVertexArrays(1, &m_vao);
m_vao = 0;
}
}
bool R3DFrameBuffers::CreateFBO(int width, int height)
{
m_width = width;
m_height = height;
m_texIDs[0] = CreateTexture(width, height); // colour buffer
m_texIDs[1] = CreateTexture(width, height); // trans layer1
m_texIDs[2] = CreateTexture(width, height); // trans layer2
glGenFramebuffers(1, &m_frameBufferID);
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
// colour attachments
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texIDs[0], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, m_texIDs[1], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, m_texIDs[2], 0);
// depth/stencil attachment
glGenRenderbuffers(1, &m_renderBufferID);
glBindRenderbuffer(GL_RENDERBUFFER, m_renderBufferID);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferID);
// check setup was successful
auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0); //created R3DFrameBuffers now disable it
CreateFBODepthCopy(width, height);
return (fboStatus == GL_FRAMEBUFFER_COMPLETE);
}
bool R3DFrameBuffers::CreateFBODepthCopy(int width, int height)
{
glGenFramebuffers(1, &m_frameBufferIDCopy);
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferIDCopy);
glGenRenderbuffers(1, &m_renderBufferIDCopy);
glBindRenderbuffer(GL_RENDERBUFFER, m_renderBufferIDCopy);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferIDCopy);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferIDCopy);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// check setup was successful
auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
return (fboStatus == GL_FRAMEBUFFER_COMPLETE);
}
void R3DFrameBuffers::StoreDepth()
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_frameBufferID);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_frameBufferIDCopy);
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
}
void R3DFrameBuffers::RestoreDepth()
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_frameBufferIDCopy);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_frameBufferID);
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
}
void R3DFrameBuffers::DestroyFBO()
{
if (m_frameBufferID) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteRenderbuffers(1, &m_renderBufferID);
glDeleteFramebuffers(1, &m_frameBufferID);
}
if (m_frameBufferIDCopy) {
glDeleteRenderbuffers(1, &m_renderBufferIDCopy);
glDeleteFramebuffers(1, &m_frameBufferIDCopy);
}
for (auto &i : m_texIDs) {
if (i) {
glDeleteTextures(1, &i);
i = 0;
}
}
m_frameBufferID = 0;
m_renderBufferID = 0;
m_frameBufferIDCopy = 0;
m_renderBufferIDCopy = 0;
m_width = 0;
m_height = 0;
}
GLuint R3DFrameBuffers::CreateTexture(int width, int height)
{
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
return texId;
}
void R3DFrameBuffers::BindTexture(Layer layer)
{
glBindTexture(GL_TEXTURE_2D, m_texIDs[(int)layer]);
}
void R3DFrameBuffers::SetFBO(Layer layer)
{
if (m_lastLayer == layer) {
return;
}
switch (layer)
{
case Layer::colour:
case Layer::trans1:
case Layer::trans2:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_COLOR_ATTACHMENT0 + (GLenum)layer };
glDrawBuffers(countof(buffers), buffers);
break;
}
case Layer::trans12:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(countof(buffers), buffers);
break;
}
case Layer::all:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(countof(buffers), buffers);
break;
}
case Layer::none:
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
break;
}
}
m_lastLayer = layer;
}
void R3DFrameBuffers::AllocShaderBase()
{
const char *vertexShader = R"glsl(
#version 410 core
// outputs
out vec2 fsTexCoord;
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0));
fsTexCoord = (vertices[gl_VertexID % 4].xy + 1.0) / 2.0;
gl_Position = vertices[gl_VertexID % 4];
}
)glsl";
const char *fragmentShader = R"glsl(
#version 410 core
// inputs
uniform sampler2D tex1; // base tex
in vec2 fsTexCoord;
// outputs
out vec4 fragColor;
void main()
{
vec4 colBase = texture(tex1, fsTexCoord);
if(colBase.a < 1.0) discard;
fragColor = colBase;
}
)glsl";
m_shaderBase.LoadShaders(vertexShader, fragmentShader);
m_shaderBase.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
}
void R3DFrameBuffers::AllocShaderTrans()
{
const char *vertexShader = R"glsl(
#version 410 core
// outputs
out vec2 fsTexCoord;
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0));
fsTexCoord = (vertices[gl_VertexID % 4].xy + 1.0) / 2.0;
gl_Position = vertices[gl_VertexID % 4];
}
)glsl";
const char *fragmentShader = R"glsl(
#version 410 core
uniform sampler2D tex1; // trans layer 1
uniform sampler2D tex2; // trans layer 2
in vec2 fsTexCoord;
// outputs
out vec4 fragColor;
void main()
{
vec4 colTrans1 = texture( tex1, fsTexCoord);
vec4 colTrans2 = texture( tex2, fsTexCoord);
if(colTrans1.a+colTrans2.a > 0.0) {
vec3 col1 = colTrans1.rgb * colTrans1.a;
vec3 col2 = colTrans2.rgb * colTrans2.a;
colTrans1 = vec4((col1+col2) / (colTrans1.a + colTrans2.a), // this is my best guess at the blending between the layers
colTrans1.a+colTrans2.a);
}
fragColor = colTrans1;
}
)glsl";
m_shaderTrans.LoadShaders(vertexShader, fragmentShader);
m_shaderTrans.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
m_shaderTrans.uniformLoc[1] = m_shaderTrans.GetUniformLocation("tex2");
}
void R3DFrameBuffers::AllocShaderWipe()
{
const char *vertexShader = R"glsl(
#version 410 core
// outputs
out vec2 fsTexCoord;
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0));
fsTexCoord = (vertices[gl_VertexID % 4].xy + 1.0) / 2.0;
gl_Position = vertices[gl_VertexID % 4];
}
)glsl";
const char *fragmentShader = R"glsl(
#version 410 core
uniform sampler2D texColor; // base colour layer
in vec2 fsTexCoord;
// outputs
layout (location = 0) out vec4 fragColor0;
layout (location = 1) out vec4 fragColor1;
void main()
{
vec4 colBase = texture(texColor, fsTexCoord);
if(colBase.a == 0.0) {
discard; // no colour pixels have been written
}
fragColor0 = vec4(0.0); // wipe these parts of the alpha buffer
fragColor1 = vec4(0.0); // since they have been overwritten by the next priority layer
}
)glsl";
m_shaderWipe.LoadShaders(vertexShader, fragmentShader);
m_shaderWipe.uniformLoc[0] = m_shaderTrans.GetUniformLocation("texColor");
}
void R3DFrameBuffers::Draw()
{
SetFBO (Layer::none); // make sure to draw on the back buffer
glViewport (0, 0, m_width, m_height); // cover the entire screen
glDisable (GL_DEPTH_TEST); // disable depth testing / writing
glDisable (GL_CULL_FACE);
glDisable (GL_BLEND);
for (int i = 0; i < countof(m_texIDs); i++) { // bind our textures to correct texture units
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, m_texIDs[i]);
}
glActiveTexture (GL_TEXTURE0);
glBindVertexArray (m_vao);
DrawBaseLayer ();
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable (GL_BLEND);
DrawAlphaLayer ();
glDisable (GL_BLEND);
glBindVertexArray (0);
}
void R3DFrameBuffers::CompositeBaseLayer()
{
SetFBO(Layer::none); // make sure to draw on the back buffer
glViewport(0, 0, m_width, m_height); // cover the entire screen
glDisable(GL_DEPTH_TEST); // disable depth testing / writing
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
for (int i = 0; i < countof(m_texIDs); i++) { // bind our textures to correct texture units
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, m_texIDs[i]);
}
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(m_vao);
DrawBaseLayer();
glBindVertexArray(0);
}
void R3DFrameBuffers::CompositeAlphaLayer()
{
SetFBO(Layer::none); // make sure to draw on the back buffer
glViewport(0, 0, m_width, m_height); // cover the entire screen
glDisable(GL_DEPTH_TEST); // disable depth testing / writing
glDisable(GL_CULL_FACE);
for (int i = 0; i < countof(m_texIDs); i++) { // bind our textures to correct texture units
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, m_texIDs[i]);
}
glActiveTexture(GL_TEXTURE0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glBindVertexArray(m_vao);
DrawAlphaLayer();
glDisable(GL_BLEND);
glBindVertexArray(0);
}
void R3DFrameBuffers::DrawOverTransLayers()
{
SetFBO(Layer::trans12); // need to write to both layers
glViewport (0, 0, m_width, m_height); // cover the entire screen
glDisable (GL_DEPTH_TEST); // disable depth testing / writing
glDisable (GL_CULL_FACE);
glDisable (GL_BLEND);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, m_texIDs[0]);
glBindVertexArray(m_vao);
m_shaderWipe.EnableShader();
glUniform1i(m_shaderWipe.uniformLoc[0], 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_shaderWipe.DisableShader();
glBindVertexArray(0);
}
void R3DFrameBuffers::DrawBaseLayer()
{
m_shaderBase.EnableShader();
glUniform1i(m_shaderTrans.uniformLoc[0], 0); // to do check this
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_shaderBase.DisableShader();
}
void R3DFrameBuffers::DrawAlphaLayer()
{
m_shaderTrans.EnableShader();
glUniform1i(m_shaderTrans.uniformLoc[0], 1); // tex unit 1
glUniform1i(m_shaderTrans.uniformLoc[1], 2); // tex unit 2
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_shaderTrans.DisableShader();
}
}