mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 07:05:40 +00:00
Draw transparent polys to separate layers and composite at the end. This solves a tonne of transparency errors we had been battling with for a long time. The model3 is strange in the fact it only supports a max of two translucent overlapped polys. They are not blended into the frame normally. Doing this means the topmost translucent polys only are visible in the scene, the equivalent of doing a depth pass first, but without the added cost.
This commit is contained in:
parent
7497e3f1a6
commit
c7ffd0a808
112
Src/Graphics/New3D/GLSLShader.cpp
Normal file
112
Src/Graphics/New3D/GLSLShader.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#include "GLSLShader.h"
|
||||
#include <stdio.h>
|
||||
|
||||
GLSLShader::GLSLShader() {
|
||||
|
||||
m_vShader = 0;
|
||||
m_fShader = 0;
|
||||
m_program = 0;
|
||||
|
||||
for (auto &i : uniformLoc) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
for (auto &i : attribLoc) {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GLSLShader::~GLSLShader() {
|
||||
|
||||
UnloadShaders();
|
||||
}
|
||||
|
||||
bool GLSLShader::LoadShaders(const char *vertexShader, const char *fragmentShader) {
|
||||
|
||||
m_program = glCreateProgram();
|
||||
m_vShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
m_fShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
glShaderSource(m_vShader, 1, &vertexShader, NULL);
|
||||
glShaderSource(m_fShader, 1, &fragmentShader, NULL);
|
||||
|
||||
glCompileShader(m_vShader);
|
||||
glCompileShader(m_fShader);
|
||||
|
||||
glAttachShader(m_program, m_vShader);
|
||||
glAttachShader(m_program, m_fShader);
|
||||
|
||||
glLinkProgram(m_program);
|
||||
|
||||
PrintShaderInfoLog(m_vShader);
|
||||
PrintShaderInfoLog(m_fShader);
|
||||
PrintProgramInfoLog(m_program);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLSLShader::UnloadShaders()
|
||||
{
|
||||
if (m_program) {
|
||||
glDeleteShader(m_vShader);
|
||||
glDeleteShader(m_fShader);
|
||||
glDeleteProgram(m_program);
|
||||
}
|
||||
|
||||
m_vShader = 0;
|
||||
m_fShader = 0;
|
||||
m_program = 0;
|
||||
}
|
||||
|
||||
void GLSLShader::EnableShader() {
|
||||
|
||||
glUseProgram(m_program);
|
||||
}
|
||||
|
||||
void GLSLShader::DisableShader() {
|
||||
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void GLSLShader::PrintShaderInfoLog(GLuint obj) {
|
||||
|
||||
int infologLength = 0;
|
||||
int charsWritten = 0;
|
||||
|
||||
glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
|
||||
|
||||
if (infologLength > 0) {
|
||||
char *infoLog = new char[infologLength];
|
||||
glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
|
||||
printf("%s\n", infoLog);
|
||||
delete[] infoLog;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSLShader::PrintProgramInfoLog(GLuint obj) {
|
||||
|
||||
int infologLength = 0;
|
||||
int charsWritten = 0;
|
||||
|
||||
glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
|
||||
|
||||
if (infologLength > 0) {
|
||||
char *infoLog = new char[infologLength];
|
||||
glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
|
||||
printf("%s\n", infoLog);
|
||||
delete[] infoLog;
|
||||
}
|
||||
}
|
||||
|
||||
int GLSLShader::GetUniformLocation(const char *str)
|
||||
{
|
||||
return glGetUniformLocation(m_program, str);
|
||||
}
|
||||
|
||||
int GLSLShader::GetAttributeLocation(const char *str)
|
||||
{
|
||||
return glGetAttribLocation(m_program, str);
|
||||
}
|
||||
|
||||
|
||||
|
34
Src/Graphics/New3D/GLSLShader.h
Normal file
34
Src/Graphics/New3D/GLSLShader.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef _GLSLSHADER_H_
|
||||
#define _GLSLSHADER_H_
|
||||
|
||||
#include "Pkgs/glew.h"
|
||||
|
||||
class GLSLShader {
|
||||
|
||||
public:
|
||||
GLSLShader();
|
||||
~GLSLShader();
|
||||
|
||||
bool LoadShaders(const char *vertexShader, const char *fragmentShader);
|
||||
void UnloadShaders();
|
||||
|
||||
void EnableShader();
|
||||
void DisableShader();
|
||||
|
||||
int GetUniformLocation(const char *str);
|
||||
int GetAttributeLocation(const char *str);
|
||||
|
||||
int uniformLoc[64];
|
||||
int attribLoc[16];
|
||||
|
||||
private:
|
||||
|
||||
void PrintShaderInfoLog(GLuint obj);
|
||||
void PrintProgramInfoLog(GLuint obj);
|
||||
|
||||
GLuint m_program;
|
||||
GLuint m_vShader;
|
||||
GLuint m_fShader;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -86,20 +86,32 @@ struct Poly // our polys are always 3 triangles, unlike the real h/w
|
|||
FVertex p3;
|
||||
};
|
||||
|
||||
enum class Layer { colour, trans1, trans2, all, none };
|
||||
|
||||
struct Mesh
|
||||
{
|
||||
//helper funcs
|
||||
bool Render(bool alpha)
|
||||
bool Render(Layer layer)
|
||||
{
|
||||
if (alpha) {
|
||||
if (!textureAlpha && !polyAlpha) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (layer)
|
||||
{
|
||||
case Layer::colour:
|
||||
if (polyAlpha) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Layer::trans1:
|
||||
if (!textureAlpha && !polyAlpha || transLSelect) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case Layer::trans2:
|
||||
if (!textureAlpha && !polyAlpha || !transLSelect) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default: // not using these types
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -117,7 +129,6 @@ struct Mesh
|
|||
float microTextureScale = 0;
|
||||
|
||||
// attributes
|
||||
bool doubleSided = false;
|
||||
bool textured = false;
|
||||
bool polyAlpha = false; // specified in the rgba colour
|
||||
bool textureAlpha = false; // use alpha in texture
|
||||
|
|
|
@ -79,6 +79,8 @@ bool CNew3D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yR
|
|||
|
||||
m_r3dShader.LoadShader();
|
||||
|
||||
m_r3dFrameBuffers.CreateFBO(totalXResParam, totalYResParam);
|
||||
|
||||
glUseProgram(0);
|
||||
|
||||
return OKAY; // OKAY ? wtf ..
|
||||
|
@ -147,14 +149,10 @@ void CNew3D::DrawScrollFog()
|
|||
}
|
||||
}
|
||||
|
||||
bool CNew3D::RenderScene(int priority, bool renderOverlay, bool alpha)
|
||||
bool CNew3D::RenderScene(int priority, bool renderOverlay, Layer layer)
|
||||
{
|
||||
bool hasOverlay = false; // (high priority polys)
|
||||
|
||||
if (alpha) {
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
|
||||
for (auto &n : m_nodes) {
|
||||
|
||||
if (n.viewport.priority != priority || n.models.empty()) {
|
||||
|
@ -188,7 +186,7 @@ bool CNew3D::RenderScene(int priority, bool renderOverlay, bool alpha)
|
|||
hasOverlay = true;
|
||||
}
|
||||
|
||||
if (!mesh.Render(alpha)) continue;
|
||||
if (!mesh.Render(layer)) continue;
|
||||
if (mesh.highPriority != renderOverlay) continue;
|
||||
|
||||
if (!matrixLoaded) {
|
||||
|
@ -236,6 +234,65 @@ bool CNew3D::RenderScene(int priority, bool renderOverlay, bool alpha)
|
|||
return hasOverlay;
|
||||
}
|
||||
|
||||
bool CNew3D::SkipLayer(int layer)
|
||||
{
|
||||
for (const auto &n : m_nodes) {
|
||||
if (n.viewport.priority == layer) {
|
||||
if (n.models.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNew3D::SetRenderStates()
|
||||
{
|
||||
m_vbo.Bind(true);
|
||||
m_r3dShader.SetShader(true);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
glEnableVertexAttribArray(3);
|
||||
glEnableVertexAttribArray(4);
|
||||
glEnableVertexAttribArray(5);
|
||||
|
||||
// before draw, specify vertex and index arrays with their offsets, offsetof is maybe evil ..
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inVertex"), 4, GL_FLOAT, GL_FALSE, sizeof(FVertex), 0);
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inNormal"), 3, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, normal));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inTexCoord"), 2, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, texcoords));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inColour"), 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FVertex), (void*)offsetof(FVertex, faceColour));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFaceNormal"), 3, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, faceNormal));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFixedShade"), 1, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, fixedShade));
|
||||
|
||||
glDepthFunc (GL_LESS);
|
||||
glEnable (GL_DEPTH_TEST);
|
||||
glDepthMask (GL_TRUE);
|
||||
glActiveTexture (GL_TEXTURE0);
|
||||
glDisable (GL_CULL_FACE); // we'll emulate this in the shader
|
||||
|
||||
glStencilFunc (GL_EQUAL, 0, 0xFF); // basically stencil test passes if the value is zero
|
||||
glStencilOp (GL_KEEP, GL_INCR, GL_INCR); // if the stencil test passes, we incriment the value
|
||||
glStencilMask (0xFF);
|
||||
}
|
||||
|
||||
void CNew3D::DisableRenderStates()
|
||||
{
|
||||
m_vbo.Bind(false);
|
||||
m_r3dShader.SetShader(false);
|
||||
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDisableVertexAttribArray(2);
|
||||
glDisableVertexAttribArray(3);
|
||||
glDisableVertexAttribArray(4);
|
||||
glDisableVertexAttribArray(5);
|
||||
}
|
||||
|
||||
void CNew3D::RenderFrame(void)
|
||||
{
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
@ -253,17 +310,6 @@ void CNew3D::RenderFrame(void)
|
|||
|
||||
DrawScrollFog(); // fog layer if applicable must be drawn here
|
||||
|
||||
glDepthFunc (GL_LEQUAL);
|
||||
glEnable (GL_DEPTH_TEST);
|
||||
glDepthMask (GL_TRUE);
|
||||
glActiveTexture (GL_TEXTURE0);
|
||||
glDisable (GL_CULL_FACE); // we'll emulate this in the shader
|
||||
glFrontFace (GL_CW);
|
||||
|
||||
glStencilFunc (GL_EQUAL, 0, 0xFF); // basically stencil test passes if the value is zero
|
||||
glStencilOp (GL_KEEP, GL_INCR, GL_INCR); // if the stencil test passes, we incriment the value
|
||||
glStencilMask (0xFF);
|
||||
|
||||
m_vbo.Bind(true);
|
||||
m_vbo.BufferSubData(MAX_ROM_POLYS*sizeof(Poly), m_polyBufferRam.size()*sizeof(Poly), m_polyBufferRam.data()); // upload all the dynamic data to GPU in one go
|
||||
|
||||
|
@ -288,59 +334,43 @@ void CNew3D::RenderFrame(void)
|
|||
}
|
||||
}
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
glEnableVertexAttribArray(3);
|
||||
glEnableVertexAttribArray(4);
|
||||
glEnableVertexAttribArray(5);
|
||||
|
||||
// before draw, specify vertex and index arrays with their offsets, offsetof is maybe evil ..
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inVertex"), 4, GL_FLOAT, GL_FALSE, sizeof(FVertex), 0);
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inNormal"), 3, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, normal));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inTexCoord"), 2, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, texcoords));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inColour"), 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FVertex), (void*)offsetof(FVertex, faceColour));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFaceNormal"), 3, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, faceNormal));
|
||||
glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFixedShade"), 1, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, fixedShade));
|
||||
|
||||
m_r3dShader.SetShader(true);
|
||||
|
||||
for (int pri = 0; pri <= 3; pri++) {
|
||||
|
||||
//==============
|
||||
bool hasOverlay;
|
||||
//==============
|
||||
|
||||
glViewport (0, 0, m_totalXRes, m_totalYRes); // clear whole viewport
|
||||
glClear (GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
|
||||
if (SkipLayer(pri)) continue;
|
||||
|
||||
m_r3dShader.DiscardAlpha(true); // chuck out alpha pixels in texture alpha only polys
|
||||
hasOverlay = RenderScene(pri, false, false);
|
||||
m_r3dShader.DiscardAlpha(false);
|
||||
hasOverlay = RenderScene(pri, false, true);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
if (hasOverlay) {
|
||||
//clear depth buffer and render high priority polys
|
||||
glViewport(0, 0, m_totalXRes, m_totalYRes); // clear whole viewport
|
||||
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
m_r3dShader.DiscardAlpha(true);
|
||||
RenderScene(pri, true, false);
|
||||
m_r3dShader.DiscardAlpha(false);
|
||||
RenderScene(pri, true, true);
|
||||
bool renderOverlay = (i == 1);
|
||||
|
||||
m_r3dFrameBuffers.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
SetRenderStates();
|
||||
|
||||
m_r3dShader.DiscardAlpha(true); // discard all translucent pixels in opaque pass
|
||||
m_r3dFrameBuffers.SetFBO(Layer::colour);
|
||||
hasOverlay = RenderScene(pri, renderOverlay, Layer::colour);
|
||||
|
||||
m_r3dShader.DiscardAlpha(false); // render only translucent pixels
|
||||
m_r3dFrameBuffers.StoreDepth(); // save depth buffer for 1st trans pass
|
||||
m_r3dFrameBuffers.SetFBO(Layer::trans1);
|
||||
RenderScene(pri, renderOverlay, Layer::trans1);
|
||||
|
||||
m_r3dFrameBuffers.RestoreDepth(); // restore depth buffer, trans layers don't seem to depth test against each other
|
||||
m_r3dFrameBuffers.SetFBO(Layer::trans2);
|
||||
RenderScene(pri, renderOverlay, Layer::trans2);
|
||||
|
||||
DisableRenderStates();
|
||||
m_r3dFrameBuffers.Draw(); // draw current layer to back buffer
|
||||
|
||||
if (!hasOverlay) break; // no high priority polys
|
||||
}
|
||||
}
|
||||
|
||||
m_r3dShader.SetShader(false); // unbind shader
|
||||
m_vbo.Bind(false);
|
||||
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDisableVertexAttribArray(2);
|
||||
glDisableVertexAttribArray(3);
|
||||
glDisableVertexAttribArray(4);
|
||||
glDisableVertexAttribArray(5);
|
||||
m_r3dFrameBuffers.DisableFBO(); // draw to back buffer normally
|
||||
}
|
||||
|
||||
void CNew3D::BeginFrame(void)
|
||||
|
@ -469,6 +499,13 @@ void CNew3D::DescendCullingNode(UINT32 addr)
|
|||
matrixOffset = node[0x03 - m_offset] & 0xFFF;
|
||||
lodTablePointer = (node[0x03 - m_offset] >> 12) & 0x7F;
|
||||
|
||||
// parse siblings
|
||||
if ((node[0x00] & 0x07) != 0x06) { // colour table seems to indicate no siblings
|
||||
if (!(sibling2Ptr & 0x1000000) && sibling2Ptr) {
|
||||
DescendCullingNode(sibling2Ptr); // no need to mask bit, would already be zero
|
||||
}
|
||||
}
|
||||
|
||||
if ((node[0x00] & 0x04)) {
|
||||
m_colorTableAddr = ((node[0x03 - m_offset] >> 19) << 0) | ((node[0x07 - m_offset] >> 28) << 13) | ((node[0x08 - m_offset] >> 25) << 17);
|
||||
m_colorTableAddr &= 0x000FFFFF; // clamp to 4MB (in words) range
|
||||
|
@ -558,13 +595,6 @@ void CNew3D::DescendCullingNode(UINT32 addr)
|
|||
|
||||
// Restore old texture offsets
|
||||
m_nodeAttribs.Pop();
|
||||
|
||||
// parse siblings
|
||||
if ((node[0x00] & 0x07) != 0x06) { // colour table seems to indicate no siblings
|
||||
if (!(sibling2Ptr & 0x1000000) && sibling2Ptr) {
|
||||
DescendCullingNode(sibling2Ptr); // no need to mask bit, would already be zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNew3D::DescendNodePtr(UINT32 nodeAddr)
|
||||
|
@ -954,7 +984,6 @@ void CNew3D::OffsetTexCoords(R3DPoly& r3dPoly, float offset[2])
|
|||
void CNew3D::SetMeshValues(SortingMesh *currentMesh, PolyHeader &ph)
|
||||
{
|
||||
//copy attributes
|
||||
currentMesh->doubleSided = false; // we will double up polys
|
||||
currentMesh->textured = ph.TexEnabled();
|
||||
currentMesh->alphaTest = ph.AlphaTest();
|
||||
currentMesh->textureAlpha = ph.TextureAlpha();
|
||||
|
@ -963,15 +992,10 @@ void CNew3D::SetMeshValues(SortingMesh *currentMesh, PolyHeader &ph)
|
|||
currentMesh->fixedShading = ph.FixedShading() && !ph.SmoothShading();
|
||||
currentMesh->highPriority = ph.HighPriority();
|
||||
currentMesh->transLSelect = ph.TranslucencyPatternSelect();
|
||||
|
||||
if (ph.Layered() || (!ph.TexEnabled() && ph.PolyAlpha())) {
|
||||
currentMesh->layered = true;
|
||||
}
|
||||
|
||||
currentMesh->layered = ph.Layered();
|
||||
currentMesh->specular = ph.SpecularEnabled();
|
||||
currentMesh->shininess = ph.Shininess();
|
||||
currentMesh->specularValue = ph.SpecularValue();
|
||||
|
||||
currentMesh->fogIntensity = ph.LightModifier();
|
||||
|
||||
if (currentMesh->textured) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
/**
|
||||
** Supermodel
|
||||
** A Sega Model 3 Arcade Emulator.
|
||||
|
@ -41,6 +42,7 @@
|
|||
#include "Vec.h"
|
||||
#include "R3DScrollFog.h"
|
||||
#include "PolyHeader.h"
|
||||
#include "R3DFrameBuffers.h"
|
||||
|
||||
namespace New3D {
|
||||
|
||||
|
@ -199,11 +201,14 @@ private:
|
|||
void CopyVertexData(const R3DPoly& r3dPoly, std::vector<Poly>& polyArray);
|
||||
void OffsetTexCoords(R3DPoly& r3dPoly, float offset[2]);
|
||||
|
||||
bool RenderScene(int priority, bool renderOverlay, bool alpha); // returns if has overlay plane
|
||||
bool RenderScene(int priority, bool renderOverlay, Layer layer); // returns if has overlay plane
|
||||
float Determinant3x3(const float m[16]);
|
||||
bool IsDynamicModel(UINT32 *data); // check if the model has a colour palette
|
||||
bool IsVROMModel(UINT32 modelAddr);
|
||||
void DrawScrollFog();
|
||||
bool SkipLayer(int layer);
|
||||
void SetRenderStates();
|
||||
void DisableRenderStates();
|
||||
|
||||
void CalcTexOffset(int offX, int offY, int page, int x, int y, int& newX, int& newY);
|
||||
|
||||
|
@ -253,6 +258,7 @@ private:
|
|||
VBO m_vbo; // large VBO to hold our poly data, start of VBO is ROM data, ram polys follow
|
||||
R3DShader m_r3dShader;
|
||||
R3DScrollFog m_r3dScrollFog;
|
||||
R3DFrameBuffers m_r3dFrameBuffers;
|
||||
|
||||
Plane m_planes[4];
|
||||
|
||||
|
|
344
Src/Graphics/New3D/R3DFrameBuffers.cpp
Normal file
344
Src/Graphics/New3D/R3DFrameBuffers.cpp
Normal file
|
@ -0,0 +1,344 @@
|
|||
#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;
|
||||
|
||||
for (auto &i : m_texIDs) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
m_lastLayer = Layer::none;
|
||||
|
||||
AllocShaderTrans();
|
||||
AllocShaderBase();
|
||||
|
||||
FBVertex vertices[4];
|
||||
vertices[0].Set(-1,-1, 0, 0);
|
||||
vertices[1].Set(-1, 1, 0, 1);
|
||||
vertices[2].Set( 1,-1, 1, 0);
|
||||
vertices[3].Set( 1, 1, 1, 1);
|
||||
|
||||
m_vbo.Create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
|
||||
}
|
||||
|
||||
R3DFrameBuffers::~R3DFrameBuffers()
|
||||
{
|
||||
DestroyFBO();
|
||||
m_shaderTrans.UnloadShaders();
|
||||
m_vbo.Destroy();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
|
||||
GLenum buffers[] = { GL_COLOR_ATTACHMENT0 + (int)layer };
|
||||
glDrawBuffers(countof(buffers), buffers);
|
||||
m_lastLayer = layer;
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::DisableFBO()
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDrawBuffer(GL_BACK);
|
||||
m_lastLayer = Layer::none;
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::Clear(GLbitfield mask)
|
||||
{
|
||||
if (m_lastLayer != Layer::all) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID); // bind frame buffer
|
||||
GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
|
||||
glDrawBuffers(countof(buffers), buffers);
|
||||
}
|
||||
|
||||
glViewport(0, 0, m_width, m_height);
|
||||
glClear(mask);
|
||||
m_lastLayer = Layer::all;
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::AllocShaderBase()
|
||||
{
|
||||
const char *vertexShader = R"glsl(
|
||||
|
||||
// inputs
|
||||
attribute vec3 inVertex;
|
||||
attribute vec2 inTexCoord;
|
||||
|
||||
// outputs
|
||||
varying vec2 fsTexCoord;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
fsTexCoord = inTexCoord;
|
||||
gl_Position = vec4(inVertex,1.0);
|
||||
};
|
||||
|
||||
)glsl";
|
||||
|
||||
const char *fragmentShader = R"glsl(
|
||||
|
||||
uniform sampler2D tex1; // base tex
|
||||
|
||||
varying vec2 fsTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 colBase = texture2D( tex1, fsTexCoord);
|
||||
if(colBase.a < 1.0) discard;
|
||||
gl_FragColor = colBase;
|
||||
};
|
||||
|
||||
)glsl";
|
||||
|
||||
m_shaderBase.LoadShaders(vertexShader, fragmentShader);
|
||||
m_shaderBase.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
|
||||
m_shaderBase.attribLoc[0] = m_shaderTrans.GetAttributeLocation("inVertex");
|
||||
m_shaderBase.attribLoc[1] = m_shaderTrans.GetAttributeLocation("inTexCoord");
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::AllocShaderTrans()
|
||||
{
|
||||
const char *vertexShader = R"glsl(
|
||||
|
||||
// inputs
|
||||
attribute vec3 inVertex;
|
||||
attribute vec2 inTexCoord;
|
||||
|
||||
// outputs
|
||||
varying vec2 fsTexCoord;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
fsTexCoord = inTexCoord;
|
||||
gl_Position = vec4(inVertex,1.0);
|
||||
};
|
||||
|
||||
)glsl";
|
||||
|
||||
const char *fragmentShader = R"glsl(
|
||||
|
||||
uniform sampler2D tex1; // trans layer 1
|
||||
uniform sampler2D tex2; // trans layer 2
|
||||
|
||||
varying vec2 fsTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 colTrans1 = texture2D( tex1, fsTexCoord);
|
||||
vec4 colTrans2 = texture2D( tex2, fsTexCoord);
|
||||
|
||||
if(colTrans1.a+colTrans2.a > 0.0) {
|
||||
vec3 col1 = (colTrans1.rgb * colTrans1.a) / ( colTrans1.a + colTrans2.a); // this is my best guess at the blending between the layers
|
||||
vec3 col2 = (colTrans2.rgb * colTrans2.a) / ( colTrans1.a + colTrans2.a);
|
||||
|
||||
colTrans1 = vec4(col1+col2,colTrans1.a+colTrans2.a);
|
||||
}
|
||||
|
||||
gl_FragColor = colTrans1;
|
||||
};
|
||||
|
||||
)glsl";
|
||||
|
||||
m_shaderTrans.LoadShaders(vertexShader, fragmentShader);
|
||||
|
||||
m_shaderTrans.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
|
||||
m_shaderTrans.uniformLoc[1] = m_shaderTrans.GetUniformLocation("tex2");
|
||||
|
||||
m_shaderTrans.attribLoc[0] = m_shaderTrans.GetAttributeLocation("inVertex");
|
||||
m_shaderTrans.attribLoc[1] = m_shaderTrans.GetAttributeLocation("inTexCoord");
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::Draw()
|
||||
{
|
||||
DisableFBO (); // 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);
|
||||
m_vbo.Bind (true);
|
||||
|
||||
DrawBaseLayer ();
|
||||
|
||||
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable (GL_BLEND);
|
||||
|
||||
DrawAlphaLayer ();
|
||||
|
||||
glDisable (GL_BLEND);
|
||||
m_vbo.Bind (false);
|
||||
}
|
||||
|
||||
void R3DFrameBuffers::DrawBaseLayer()
|
||||
{
|
||||
m_shaderBase.EnableShader();
|
||||
glUniform1i(m_shaderTrans.uniformLoc[0], 0);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glVertexAttribPointer(m_shaderTrans.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts));
|
||||
glVertexAttribPointer(m_shaderTrans.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords));
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
|
||||
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
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glVertexAttribPointer(m_shaderTrans.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts));
|
||||
glVertexAttribPointer(m_shaderTrans.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords));
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
|
||||
m_shaderTrans.DisableShader();
|
||||
}
|
||||
|
||||
}
|
73
Src/Graphics/New3D/R3DFrameBuffers.h
Normal file
73
Src/Graphics/New3D/R3DFrameBuffers.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
#ifndef FBO_H
|
||||
#define FBO_H
|
||||
|
||||
#include "Pkgs/glew.h"
|
||||
#include "VBO.h"
|
||||
#include "GLSLShader.h"
|
||||
#include "Model.h"
|
||||
|
||||
namespace New3D {
|
||||
|
||||
class R3DFrameBuffers {
|
||||
|
||||
public:
|
||||
R3DFrameBuffers();
|
||||
~R3DFrameBuffers();
|
||||
|
||||
void Draw(); // draw and composite the transparent layers
|
||||
|
||||
bool CreateFBO(int width, int height);
|
||||
void DestroyFBO();
|
||||
|
||||
void BindTexture(Layer layer);
|
||||
void SetFBO(Layer layer);
|
||||
void DisableFBO();
|
||||
void Clear(GLbitfield mask);
|
||||
void StoreDepth();
|
||||
void RestoreDepth();
|
||||
|
||||
private:
|
||||
|
||||
struct FBVertex
|
||||
{
|
||||
void Set(float x, float y, float s, float t)
|
||||
{
|
||||
texCoords[0] = s;
|
||||
texCoords[1] = t;
|
||||
verts[0] = x;
|
||||
verts[1] = y;
|
||||
verts[2] = 0; // z = 0
|
||||
}
|
||||
|
||||
float texCoords[2];
|
||||
float verts[3];
|
||||
};
|
||||
|
||||
bool CreateFBODepthCopy(int width, int height);
|
||||
GLuint CreateTexture(int width, int height);
|
||||
void AllocShaderTrans();
|
||||
void AllocShaderBase();
|
||||
|
||||
void DrawBaseLayer();
|
||||
void DrawAlphaLayer();
|
||||
|
||||
GLuint m_frameBufferID;
|
||||
GLuint m_renderBufferID;
|
||||
GLuint m_texIDs[3];
|
||||
GLuint m_frameBufferIDCopy;
|
||||
GLuint m_renderBufferIDCopy;
|
||||
Layer m_lastLayer;
|
||||
int m_width;
|
||||
int m_height;
|
||||
|
||||
// shaders
|
||||
GLSLShader m_shaderBase;
|
||||
GLSLShader m_shaderTrans;
|
||||
|
||||
// vertices for fbo
|
||||
VBO m_vbo;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -114,11 +114,18 @@ vec4 GetTextureValue()
|
|||
}
|
||||
}
|
||||
|
||||
if(discardAlpha && textureAlpha) {
|
||||
if(textureAlpha) {
|
||||
if(discardAlpha) { // opaque 1st pass
|
||||
if (tex1Data.a < 1.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
else { // transparent 2nd pass
|
||||
if ((tex1Data.a * fsColor.a) >= 1.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textureAlpha == false) {
|
||||
tex1Data.a = 1.0;
|
||||
|
|
|
@ -311,11 +311,13 @@ xcopy /D /Y "$(ProjectDir)\SDL\$(Platform)\$(Configuration)\SDL.dll" "$(TargetDi
|
|||
<ClCompile Include="..\Src\Graphics\Legacy3D\Legacy3D.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\Legacy3D\Models.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\Legacy3D\TextureRefs.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\GLSLShader.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\Mat4.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\Model.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\New3D.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\PolyHeader.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\R3DFloat.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\R3DFrameBuffers.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\R3DScrollFog.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\R3DShader.cpp" />
|
||||
<ClCompile Include="..\Src\Graphics\New3D\Texture.cpp" />
|
||||
|
@ -537,6 +539,7 @@ xcopy /D /Y "$(ProjectDir)\SDL\$(Platform)\$(Configuration)\SDL.dll" "$(TargetDi
|
|||
<ClInclude Include="..\Src\Graphics\Legacy3D\Legacy3D.h" />
|
||||
<ClInclude Include="..\Src\Graphics\Legacy3D\Shaders3D.h" />
|
||||
<ClInclude Include="..\Src\Graphics\Legacy3D\TextureRefs.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\GLSLShader.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\Mat4.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\Model.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\New3D.h" />
|
||||
|
@ -544,6 +547,7 @@ xcopy /D /Y "$(ProjectDir)\SDL\$(Platform)\$(Configuration)\SDL.dll" "$(TargetDi
|
|||
<ClInclude Include="..\Src\Graphics\New3D\PolyHeader.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DData.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DFloat.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DFrameBuffers.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DScrollFog.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DShader.h" />
|
||||
<ClInclude Include="..\Src\Graphics\New3D\Texture.h" />
|
||||
|
|
|
@ -464,6 +464,12 @@
|
|||
<ClCompile Include="..\Src\Debugger\DebuggerIO.cpp">
|
||||
<Filter>Source Files\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Src\Graphics\New3D\R3DFrameBuffers.cpp">
|
||||
<Filter>Source Files\Graphics\New</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Src\Graphics\New3D\GLSLShader.cpp">
|
||||
<Filter>Source Files\Graphics\New</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="..\Src\CPU\68K\Turbo68K\Turbo68K.asm">
|
||||
|
@ -862,6 +868,12 @@
|
|||
<ClInclude Include="..\Src\Debugger\DebuggerIO.h">
|
||||
<Filter>Header Files\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Src\Graphics\New3D\R3DFrameBuffers.h">
|
||||
<Filter>Source Files\Graphics\New</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Src\Graphics\New3D\GLSLShader.h">
|
||||
<Filter>Source Files\Graphics\New</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="..\Src\Debugger\ReadMe.txt">
|
||||
|
|
Loading…
Reference in a new issue