Play-/Source/gs/GSH_OpenGL/GSH_OpenGL.cpp

2586 lines
75 KiB
C++
Raw Normal View History

#include <stdio.h>
#include <assert.h>
2019-08-17 13:51:31 -04:00
#include <cstring>
#include <math.h>
2015-07-31 14:06:45 -04:00
#include "../../Log.h"
#include "../../AppConfig.h"
#include "../GsPixelFormats.h"
#include "../GsTransferRange.h"
#include "GSH_OpenGL.h"
2019-07-23 12:28:50 -04:00
#ifdef USE_DUALSOURCE_BLENDING
//Dual source blending constants
#define BLEND_SRC_ALPHA GL_SRC1_ALPHA
#define BLEND_ONE_MINUS_SRC_ALPHA GL_ONE_MINUS_SRC1_ALPHA
2019-07-23 12:28:50 -04:00
#else
//Standard blending constants
#define BLEND_SRC_ALPHA GL_SRC_ALPHA
#define BLEND_ONE_MINUS_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
#endif
2016-02-26 19:12:50 -05:00
#define NUM_SAMPLES 8
#define FRAMEBUFFER_HEIGHT 1024
2016-02-26 19:12:50 -05:00
// clang-format off
2016-03-27 20:42:58 -04:00
const GLenum CGSH_OpenGL::g_nativeClampModes[CGSHandler::CLAMP_MODE_MAX] =
{
GL_REPEAT,
GL_CLAMP_TO_EDGE,
GL_REPEAT,
GL_REPEAT
};
2016-03-27 20:42:58 -04:00
const unsigned int CGSH_OpenGL::g_shaderClampModes[CGSHandler::CLAMP_MODE_MAX] =
{
TEXTURE_CLAMP_MODE_STD,
TEXTURE_CLAMP_MODE_CLAMP,
TEXTURE_CLAMP_MODE_REGION_CLAMP,
TEXTURE_CLAMP_MODE_REGION_REPEAT
};
const unsigned int CGSH_OpenGL::g_alphaTestInverse[CGSHandler::ALPHA_TEST_MAX] =
{
ALPHA_TEST_ALWAYS,
ALPHA_TEST_NEVER,
ALPHA_TEST_GEQUAL,
ALPHA_TEST_GREATER,
ALPHA_TEST_NOTEQUAL,
ALPHA_TEST_LESS,
ALPHA_TEST_LEQUAL,
ALPHA_TEST_EQUAL
};
// clang-format on
static uint32 MakeColor(uint8 r, uint8 g, uint8 b, uint8 a)
{
return (a << 24) | (b << 16) | (g << 8) | (r);
}
2019-12-29 17:35:52 +01:00
CGSH_OpenGL::CGSH_OpenGL(bool gsThreaded)
: CGSHandler(gsThreaded)
, m_pCvtBuffer(nullptr)
{
RegisterPreferences();
LoadPreferences();
m_pCvtBuffer = new uint8[CVTBUFFERSIZE];
memset(&m_renderState, 0, sizeof(m_renderState));
m_vertexBuffer.reserve(VERTEX_BUFFER_SIZE);
}
CGSH_OpenGL::~CGSH_OpenGL()
{
2018-04-30 21:01:23 +01:00
delete[] m_pCvtBuffer;
}
void CGSH_OpenGL::InitializeImpl()
{
InitializeRC();
m_nVtxCount = 0;
for(unsigned int i = 0; i < MAX_PALETTE_CACHE; i++)
{
m_paletteCache.push_back(PalettePtr(new CPalette()));
}
m_renderState.isValid = false;
m_validGlState = 0;
}
void CGSH_OpenGL::ReleaseImpl()
{
ResetImpl();
m_paletteCache.clear();
m_shaders.clear();
m_presentProgram.reset();
m_presentVertexBuffer.Reset();
m_presentVertexArray.Reset();
m_copyToFbProgram.reset();
m_copyToFbTexture.Reset();
m_copyToFbVertexBuffer.Reset();
m_copyToFbVertexArray.Reset();
m_primBuffer.Reset();
m_primVertexArray.Reset();
m_vertexParamsBuffer.Reset();
m_fragmentParamsBuffer.Reset();
}
void CGSH_OpenGL::ResetImpl()
{
LoadPreferences();
m_textureCache.Flush();
PalCache_Flush();
m_framebuffers.clear();
m_depthbuffers.clear();
m_vertexBuffer.clear();
m_renderState.isValid = false;
m_validGlState = 0;
m_nVtxCount = 0;
m_drawingToDepth = false;
2017-02-09 22:18:58 -05:00
m_primitiveType = PRIM_INVALID;
m_pendingPrim = false;
m_pendingPrimValue = 0;
}
void CGSH_OpenGL::FlipImpl(const DISPLAY_INFO& dispInfo)
{
FlushVertexBuffer();
m_renderState.isValid = false;
2016-02-24 20:59:01 -05:00
m_validGlState = 0;
const auto& dispLayer = dispInfo.layers[0];
FramebufferPtr framebuffer;
if(dispLayer.enabled)
{
for(const auto& candidateFramebuffer : m_framebuffers)
{
if(
(candidateFramebuffer->m_basePtr == dispLayer.bufPtr) &&
(GetFramebufferBitDepth(candidateFramebuffer->m_psm) == GetFramebufferBitDepth(dispLayer.psm)) &&
(candidateFramebuffer->m_width == dispLayer.bufWidth))
{
//We have a winner
framebuffer = candidateFramebuffer;
break;
}
}
if(!framebuffer && (dispLayer.bufWidth != 0))
{
framebuffer = FramebufferPtr(new CFramebuffer(dispLayer.bufPtr, dispLayer.bufWidth, FRAMEBUFFER_HEIGHT, dispLayer.psm, m_fbScale, m_multisampleEnabled));
m_framebuffers.push_back(framebuffer);
PopulateFramebuffer(framebuffer);
}
}
if(framebuffer)
{
CommitFramebufferDirtyPages(framebuffer, 0, dispLayer.height);
2016-02-26 19:12:50 -05:00
if(m_multisampleEnabled)
{
ResolveFramebufferMultisample(framebuffer, m_fbScale);
}
}
//Clear all of our output framebuffer
{
glBindFramebuffer(GL_FRAMEBUFFER, m_presentFramebuffer);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_SCISSOR_TEST);
glClearColor(0, 0, 0, 0);
glViewport(0, 0, m_presentationParams.windowWidth, m_presentationParams.windowHeight);
glClear(GL_COLOR_BUFFER_BIT);
}
2020-01-25 20:59:52 -05:00
auto presentationViewport = GetPresentationViewport();
glViewport(presentationViewport.offsetX, presentationViewport.offsetY,
2020-01-31 09:34:48 -05:00
presentationViewport.width, presentationViewport.height);
if(framebuffer)
{
2023-03-06 15:40:40 -05:00
float u1 = static_cast<float>(dispLayer.width) / static_cast<float>(framebuffer->m_width);
float v1 = static_cast<float>(dispLayer.height) / static_cast<float>(framebuffer->m_height);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, framebuffer->m_texture);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
glUseProgram(*m_presentProgram);
assert(m_presentTextureUniform != -1);
glUniform1i(m_presentTextureUniform, 0);
assert(m_presentTexCoordScaleUniform != -1);
glUniform2f(m_presentTexCoordScaleUniform, u1, v1);
glBindBuffer(GL_ARRAY_BUFFER, m_presentVertexBuffer);
glBindVertexArray(m_presentVertexArray);
#ifdef _DEBUG
m_presentProgram->Validate();
#endif
glDrawArrays(GL_TRIANGLES, 0, 3);
}
CHECKGLERROR();
static bool g_dumpFramebuffers = false;
if(g_dumpFramebuffers)
{
for(const auto& framebuffer : m_framebuffers)
{
glBindTexture(GL_TEXTURE_2D, framebuffer->m_texture);
2016-01-08 23:19:57 -05:00
DumpTexture(framebuffer->m_width * m_fbScale, framebuffer->m_height * m_fbScale, framebuffer->m_basePtr);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
PresentBackbuffer();
CGSHandler::FlipImpl(dispInfo);
}
void CGSH_OpenGL::LoadState(Framework::CZipArchiveReader& archive)
{
CGSHandler::LoadState(archive);
2019-12-29 17:35:52 +01:00
SendGSCall(
2018-04-30 21:01:23 +01:00
[this]() {
m_textureCache.InvalidateRange(0, RAMSIZE);
});
}
void CGSH_OpenGL::RegisterPreferences()
{
CGSHandler::RegisterPreferences();
2019-01-23 02:39:28 +00:00
CAppConfig::GetInstance().RegisterPreferenceInteger(PREF_CGSH_OPENGL_RESOLUTION_FACTOR, 1);
CAppConfig::GetInstance().RegisterPreferenceBoolean(PREF_CGSH_OPENGL_FORCEBILINEARTEXTURES, false);
}
void CGSH_OpenGL::NotifyPreferencesChangedImpl()
{
LoadPreferences();
m_textureCache.Flush();
PalCache_Flush();
m_framebuffers.clear();
m_depthbuffers.clear();
CGSHandler::NotifyPreferencesChangedImpl();
}
void CGSH_OpenGL::LoadPreferences()
{
2019-01-23 02:39:28 +00:00
m_fbScale = CAppConfig::GetInstance().GetPreferenceInteger(PREF_CGSH_OPENGL_RESOLUTION_FACTOR);
m_forceBilinearTextures = CAppConfig::GetInstance().GetPreferenceBoolean(PREF_CGSH_OPENGL_FORCEBILINEARTEXTURES);
}
void CGSH_OpenGL::InitializeRC()
{
//Initialize basic stuff
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepthf(0.0f);
CheckExtensions();
2017-01-16 23:56:36 -05:00
SetupTextureUpdaters();
m_presentProgram = GeneratePresentProgram();
m_presentVertexBuffer = GeneratePresentVertexBuffer();
m_presentVertexArray = GeneratePresentVertexArray();
m_presentTextureUniform = glGetUniformLocation(*m_presentProgram, "g_texture");
m_presentTexCoordScaleUniform = glGetUniformLocation(*m_presentProgram, "g_texCoordScale");
m_copyToFbProgram = GenerateCopyToFbProgram();
m_copyToFbTexture = Framework::OpenGl::CTexture::Create();
m_copyToFbVertexBuffer = GenerateCopyToFbVertexBuffer();
m_copyToFbVertexArray = GenerateCopyToFbVertexArray();
m_copyToFbSrcPositionUniform = glGetUniformLocation(*m_copyToFbProgram, "g_srcPosition");
m_copyToFbSrcSizeUniform = glGetUniformLocation(*m_copyToFbProgram, "g_srcSize");
m_primBuffer = Framework::OpenGl::CBuffer::Create();
m_primVertexArray = GeneratePrimVertexArray();
m_vertexParamsBuffer = GenerateUniformBlockBuffer(sizeof(VERTEXPARAMS));
m_fragmentParamsBuffer = GenerateUniformBlockBuffer(sizeof(FRAGMENTPARAMS));
PresentBackbuffer();
CHECKGLERROR();
}
void CGSH_OpenGL::CheckExtensions()
{
GLint numExtensions = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
for(GLint i = 0; i < numExtensions; i++)
{
const char* extensionName = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
if(!strcmp(extensionName, "GL_EXT_shader_framebuffer_fetch"))
{
m_hasFramebufferFetchExtension = true;
}
}
}
Framework::OpenGl::CBuffer CGSH_OpenGL::GeneratePresentVertexBuffer()
{
auto buffer = Framework::OpenGl::CBuffer::Create();
2018-04-30 12:00:43 -04:00
// clang-format off
static const float bufferContents[] =
{
//Pos UV
-1.0f, -1.0f, 0.0f, 1.0f,
-1.0f, 3.0f, 0.0f, -1.0f,
3.0f, -1.0f, 2.0f, 1.0f,
};
2018-04-30 12:00:43 -04:00
// clang-format on
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(bufferContents), bufferContents, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CHECKGLERROR();
return buffer;
}
Framework::OpenGl::CVertexArray CGSH_OpenGL::GeneratePresentVertexArray()
{
auto vertexArray = Framework::OpenGl::CVertexArray::Create();
glBindVertexArray(vertexArray);
2018-04-30 21:01:23 +01:00
glBindBuffer(GL_ARRAY_BUFFER, m_presentVertexBuffer);
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION), 2, GL_FLOAT,
GL_FALSE, sizeof(float) * 4, reinterpret_cast<const GLvoid*>(0));
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD), 2, GL_FLOAT,
GL_FALSE, sizeof(float) * 4, reinterpret_cast<const GLvoid*>(8));
glBindVertexArray(0);
CHECKGLERROR();
return vertexArray;
}
Framework::OpenGl::CBuffer CGSH_OpenGL::GenerateCopyToFbVertexBuffer()
{
auto buffer = Framework::OpenGl::CBuffer::Create();
2018-04-30 12:00:43 -04:00
// clang-format off
static const float bufferContents[] =
{
//Pos UV
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
};
2018-04-30 12:00:43 -04:00
// clang-format on
2018-04-30 21:01:23 +01:00
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(bufferContents), bufferContents, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CHECKGLERROR();
return buffer;
}
Framework::OpenGl::CVertexArray CGSH_OpenGL::GenerateCopyToFbVertexArray()
{
auto vertexArray = Framework::OpenGl::CVertexArray::Create();
glBindVertexArray(vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, m_copyToFbVertexBuffer);
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION), 2, GL_FLOAT,
GL_FALSE, sizeof(float) * 4, reinterpret_cast<const GLvoid*>(0));
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD), 2, GL_FLOAT,
GL_FALSE, sizeof(float) * 4, reinterpret_cast<const GLvoid*>(8));
glBindVertexArray(0);
CHECKGLERROR();
return vertexArray;
}
Framework::OpenGl::CVertexArray CGSH_OpenGL::GeneratePrimVertexArray()
{
auto vertexArray = Framework::OpenGl::CVertexArray::Create();
glBindVertexArray(vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, m_primBuffer);
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION));
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::POSITION), 2, GL_FLOAT,
2018-04-30 21:01:23 +01:00
GL_FALSE, sizeof(PRIM_VERTEX), reinterpret_cast<const GLvoid*>(offsetof(PRIM_VERTEX, x)));
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::DEPTH));
glVertexAttribIPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::DEPTH), 1, GL_UNSIGNED_INT,
sizeof(PRIM_VERTEX), reinterpret_cast<const GLvoid*>(offsetof(PRIM_VERTEX, z)));
2019-08-06 22:08:46 -04:00
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::COLOR));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::COLOR), 4, GL_UNSIGNED_BYTE,
GL_TRUE, sizeof(PRIM_VERTEX), reinterpret_cast<const GLvoid*>(offsetof(PRIM_VERTEX, color)));
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::TEXCOORD), 3, GL_FLOAT,
GL_FALSE, sizeof(PRIM_VERTEX), reinterpret_cast<const GLvoid*>(offsetof(PRIM_VERTEX, s)));
glEnableVertexAttribArray(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::FOG));
2018-04-30 21:01:23 +01:00
glVertexAttribPointer(static_cast<GLuint>(PRIM_VERTEX_ATTRIB::FOG), 1, GL_FLOAT,
GL_FALSE, sizeof(PRIM_VERTEX), reinterpret_cast<const GLvoid*>(offsetof(PRIM_VERTEX, f)));
glBindVertexArray(0);
CHECKGLERROR();
return vertexArray;
}
Framework::OpenGl::CBuffer CGSH_OpenGL::GenerateUniformBlockBuffer(size_t blockSize)
{
auto uniformBlockBuffer = Framework::OpenGl::CBuffer::Create();
glBindBuffer(GL_UNIFORM_BUFFER, uniformBlockBuffer);
glBufferData(GL_UNIFORM_BUFFER, blockSize, nullptr, GL_STREAM_DRAW);
CHECKGLERROR();
return uniformBlockBuffer;
}
void CGSH_OpenGL::MakeLinearZOrtho(float* matrix, float left, float right, float bottom, float top)
{
2018-04-30 21:01:23 +01:00
matrix[0] = 2.0f / (right - left);
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = 2.0f / (top - bottom);
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
2018-04-30 21:01:23 +01:00
matrix[12] = -(right + left) / (right - left);
matrix[13] = -(top + bottom) / (top - bottom);
matrix[14] = 0;
matrix[15] = 1;
}
/////////////////////////////////////////////////////////////
// Context Unpacking
/////////////////////////////////////////////////////////////
Framework::OpenGl::ProgramPtr CGSH_OpenGL::GetShaderFromCaps(const SHADERCAPS& shaderCaps)
{
auto shaderIterator = m_shaders.find(shaderCaps);
if(shaderIterator == m_shaders.end())
{
auto shader = GenerateShader(shaderCaps);
glUseProgram(*shader);
m_validGlState &= ~GLSTATE_PROGRAM;
auto textureUniform = glGetUniformLocation(*shader, "g_texture");
if(textureUniform != -1)
{
glUniform1i(textureUniform, 0);
}
auto paletteUniform = glGetUniformLocation(*shader, "g_palette");
if(paletteUniform != -1)
{
glUniform1i(paletteUniform, 1);
}
auto vertexParamsUniformBlock = glGetUniformBlockIndex(*shader, "VertexParams");
if(vertexParamsUniformBlock != GL_INVALID_INDEX)
{
glUniformBlockBinding(*shader, vertexParamsUniformBlock, 0);
}
auto fragmentParamsUniformBlock = glGetUniformBlockIndex(*shader, "FragmentParams");
if(fragmentParamsUniformBlock != GL_INVALID_INDEX)
{
glUniformBlockBinding(*shader, fragmentParamsUniformBlock, 1);
}
CHECKGLERROR();
m_shaders.insert(std::make_pair(shaderCaps, shader));
shaderIterator = m_shaders.find(shaderCaps);
}
return shaderIterator->second;
}
void CGSH_OpenGL::SetRenderingContext(uint64 primReg)
{
auto prim = make_convertible<PRMODE>(primReg);
unsigned int context = prim.nContext;
uint64 testReg = m_nReg[GS_REG_TEST_1 + context];
uint64 frameReg = m_nReg[GS_REG_FRAME_1 + context];
uint64 alphaReg = m_nReg[GS_REG_ALPHA_1 + context];
uint64 zbufReg = m_nReg[GS_REG_ZBUF_1 + context];
uint64 tex0Reg = m_nReg[GS_REG_TEX0_1 + context];
uint64 tex1Reg = m_nReg[GS_REG_TEX1_1 + context];
uint64 texAReg = m_nReg[GS_REG_TEXA];
uint64 clampReg = m_nReg[GS_REG_CLAMP_1 + context];
uint64 fogColReg = m_nReg[GS_REG_FOGCOL];
uint64 scissorReg = m_nReg[GS_REG_SCISSOR_1 + context];
//--------------------------------------------------------
//Get shader caps
//--------------------------------------------------------
auto shaderCaps = make_convertible<SHADERCAPS>(0);
FillShaderCapsFromTexture(shaderCaps, tex0Reg, tex1Reg, texAReg, clampReg);
FillShaderCapsFromTest(shaderCaps, testReg);
FillShaderCapsFromAlpha(shaderCaps, prim.nAlpha != 0, alphaReg);
if(prim.nFog)
{
shaderCaps.hasFog = 1;
}
if(!prim.nTexture)
{
shaderCaps.texSourceMode = TEXTURE_SOURCE_MODE_NONE;
}
//--------------------------------------------------------
//Check if a different shader is needed
//--------------------------------------------------------
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(static_cast<uint32>(m_renderState.shaderCaps) != static_cast<uint32>(shaderCaps)))
{
FlushVertexBuffer();
m_renderState.shaderCaps = shaderCaps;
}
//--------------------------------------------------------
//Set render states
//--------------------------------------------------------
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(m_renderState.primReg != primReg))
{
FlushVertexBuffer();
if(!m_hasFramebufferFetchExtension)
{
m_renderState.blendEnabled = ((prim.nAlpha != 0) && m_alphaBlendingEnabled) ? GL_TRUE : GL_FALSE;
m_validGlState &= ~GLSTATE_BLEND;
}
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(m_renderState.alphaReg != alphaReg))
{
FlushVertexBuffer();
SetupBlendingFunction(alphaReg);
CHECKGLERROR();
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(m_renderState.testReg != testReg))
{
FlushVertexBuffer();
SetupTestFunctions(testReg);
CHECKGLERROR();
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(m_renderState.zbufReg != zbufReg) ||
(m_renderState.testReg != testReg))
{
FlushVertexBuffer();
SetupDepthBuffer(zbufReg, testReg);
CHECKGLERROR();
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
!m_renderState.isFramebufferStateValid ||
(m_renderState.frameReg != frameReg) ||
(m_renderState.zbufReg != zbufReg) ||
(m_renderState.scissorReg != scissorReg) ||
(m_renderState.testReg != testReg))
{
FlushVertexBuffer();
SetupFramebuffer(frameReg, zbufReg, scissorReg, testReg);
CHECKGLERROR();
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
!m_renderState.isTextureStateValid ||
(m_renderState.tex0Reg != tex0Reg) ||
(m_renderState.tex1Reg != tex1Reg) ||
(m_renderState.texAReg != texAReg) ||
(m_renderState.clampReg != clampReg) ||
(m_renderState.primReg != primReg))
{
FlushVertexBuffer();
SetupTexture(primReg, tex0Reg, tex1Reg, texAReg, clampReg);
CHECKGLERROR();
}
if(!m_renderState.isValid ||
2018-04-30 21:01:23 +01:00
(m_renderState.fogColReg != fogColReg))
{
FlushVertexBuffer();
SetupFogColor(fogColReg);
CHECKGLERROR();
}
2016-02-12 10:17:20 -05:00
auto offset = make_convertible<XYOFFSET>(m_nReg[GS_REG_XYOFFSET_1 + context]);
m_nPrimOfsX = offset.GetX();
m_nPrimOfsY = offset.GetY();
2018-04-30 21:01:23 +01:00
CHECKGLERROR();
2018-04-30 21:01:23 +01:00
m_renderState.isValid = true;
m_renderState.isTextureStateValid = true;
m_renderState.isFramebufferStateValid = true;
2018-04-30 21:01:23 +01:00
m_renderState.primReg = primReg;
m_renderState.alphaReg = alphaReg;
m_renderState.testReg = testReg;
m_renderState.zbufReg = zbufReg;
m_renderState.scissorReg = scissorReg;
m_renderState.frameReg = frameReg;
m_renderState.tex0Reg = tex0Reg;
m_renderState.tex1Reg = tex1Reg;
m_renderState.texAReg = texAReg;
m_renderState.clampReg = clampReg;
m_renderState.fogColReg = fogColReg;
}
void CGSH_OpenGL::SetupBlendingFunction(uint64 alphaReg)
{
auto alpha = make_convertible<ALPHA>(alphaReg);
if(m_hasFramebufferFetchExtension)
{
m_fragmentParams.alphaFix = (static_cast<float>(alpha.nFix) / 255.f);
m_validGlState &= ~GLSTATE_FRAGMENT_PARAMS;
return;
}
int nFunction = GL_FUNC_ADD;
if((alpha.nA == alpha.nB) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//ab*0 (when a == b) - Cs
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
}
else if((alpha.nA == alpha.nB) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//ab*1 (when a == b) - Cd
glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_ONE, GL_ZERO);
}
else if((alpha.nA == alpha.nB) && (alpha.nD == ALPHABLEND_ABD_ZERO))
{
//ab*2 (when a == b) - Zero
glBlendFuncSeparate(GL_ZERO, GL_ZERO, GL_ONE, GL_ZERO);
}
2015-12-22 21:16:50 -05:00
else if((alpha.nA == ALPHABLEND_ABD_CS) && (alpha.nB == ALPHABLEND_ABD_CD) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CD))
{
2015-12-22 21:16:50 -05:00
//0101 - Cs * As + Cd * (1 - As)
glBlendFuncSeparate(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 0) && (alpha.nB == 1) && (alpha.nC == 1) && (alpha.nD == 1))
{
//Cs * Ad + Cd * (1 - Ad)
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 0) && (alpha.nB == 1) && (alpha.nC == 2) && (alpha.nD == 1))
{
if(alpha.nFix == 0x80)
{
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
}
else
{
//Source alpha value is implied in the formula
//As = FIX / 0x80
glBlendColor(0.0f, 0.0f, 0.0f, (float)alpha.nFix / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
}
else if((alpha.nA == 0) && (alpha.nB == 2) && (alpha.nC == 0) && (alpha.nD == 1))
{
glBlendFuncSeparate(BLEND_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 0) && (alpha.nB == 2) && (alpha.nC == 0) && (alpha.nD == 2))
{
//Cs * As
glBlendFuncSeparate(BLEND_SRC_ALPHA, GL_ZERO, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 0) && (alpha.nB == 2) && (alpha.nC == 1) && (alpha.nD == 1))
{
//Cs * Ad + Cd
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 0) && (alpha.nB == 2) && (alpha.nC == 2) && (alpha.nD == 1))
{
if(alpha.nFix == 0x80)
{
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ZERO);
}
else
{
//Cs * FIX + Cd
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
}
2017-08-25 20:41:01 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CS) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_ZERO))
{
//0222 - Cs * FIX
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ZERO, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 1) && (alpha.nB == 0) && (alpha.nC == 0) && (alpha.nD == 0))
{
//(Cd - Cs) * As + Cs
glBlendFuncSeparate(BLEND_ONE_MINUS_SRC_ALPHA, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//1001 -> (Cd - Cs) * As + Cd (Inaccurate, needs +1 to As)
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendFuncSeparate(BLEND_SRC_ALPHA, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_ZERO))
{
//1002 -> (Cd - Cs) * As
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendFuncSeparate(BLEND_SRC_ALPHA, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
2016-10-09 15:57:44 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AD) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//1010 -> Cs * (1 - Ad) + Cd * Ad
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ZERO);
}
2017-08-25 20:41:01 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//1020 -> Cs * (1 - FIX) + Cd * FIX
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//1021 -> (Cd - Cs) * FIX + Cd
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendColor(0.0f, 0.0f, 0.0f, (float)alpha.nFix / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 1) && (alpha.nB == 0) && (alpha.nC == 2) && (alpha.nD == 2))
{
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendColor(0.0f, 0.0f, 0.0f, (float)alpha.nFix / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 1) && (alpha.nB == 2) && (alpha.nC == 0) && (alpha.nD == 0))
{
//Cd * As + Cs
glBlendFuncSeparate(GL_ONE, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//1201 -> Cd * (As + 1)
2020-06-10 18:34:25 -04:00
//Relies on colorOutputWhite shader cap
glBlendFuncSeparate(GL_DST_COLOR, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
2015-12-22 21:35:34 -05:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_ZERO))
{
2015-12-22 21:35:34 -05:00
//1202 - Cd * As
glBlendFuncSeparate(GL_ZERO, BLEND_SRC_ALPHA, GL_ONE, GL_ZERO);
}
2021-03-19 07:38:18 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_AD) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//1210 - Cs + (Cd * Ad)
glBlendFuncSeparate(GL_ONE, GL_DST_ALPHA, GL_ONE, GL_ZERO);
}
2020-09-25 16:35:23 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_AD) && (alpha.nD == ALPHABLEND_ABD_ZERO))
{
//1212 - Cd * Ad
glBlendFuncSeparate(GL_ZERO, GL_DST_ALPHA, GL_ONE, GL_ZERO);
}
2015-07-28 03:14:33 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//1220 -> Cd * FIX + Cs
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_ONE, GL_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
2020-06-12 09:11:50 -04:00
else if((alpha.nA == ALPHABLEND_ABD_CD) && (alpha.nB == ALPHABLEND_ABD_ZERO) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CD))
2020-06-11 09:29:54 -04:00
{
//1221 -> Cd * (1 + FIX)
//Relies on colorOutputWhite shader cap
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_DST_COLOR, GL_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == 1) && (alpha.nB == 2) && (alpha.nC == 2) && (alpha.nD == 2))
{
2020-06-08 17:26:30 -04:00
//1222 -> Cd * FIX
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_ZERO, GL_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CS))
{
//2000 -> Cs * (1 - As)
glBlendFuncSeparate(BLEND_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE, GL_ZERO);
}
2017-05-29 21:19:31 -04:00
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CD))
{
2017-05-29 21:19:31 -04:00
//2001 -> Cd - Cs * As
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendFuncSeparate(BLEND_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
2017-08-23 21:44:55 -04:00
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_AD) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//2011 -> Cd - Cs * Ad
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
2015-07-18 20:49:28 -04:00
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CS) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//2021 -> Cd - Cs * FIX
nFunction = GL_FUNC_REVERSE_SUBTRACT;
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
}
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CD) && (alpha.nC == ALPHABLEND_C_AS) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//2101 -> Cd * (1 - As)
glBlendFuncSeparate(GL_ZERO, BLEND_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
2020-12-23 16:04:59 -05:00
else if((alpha.nA == ALPHABLEND_ABD_ZERO) && (alpha.nB == ALPHABLEND_ABD_CD) && (alpha.nC == ALPHABLEND_C_FIX) && (alpha.nD == ALPHABLEND_ABD_CD))
{
//2121 -> Cd * (1 - FIX)
glBlendColor(0, 0, 0, static_cast<float>(alpha.nFix) / 128.0f);
glBlendFuncSeparate(GL_ZERO, GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
}
else
{
assert(0);
//Default blending
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
}
glBlendEquationSeparate(nFunction, GL_FUNC_ADD);
}
void CGSH_OpenGL::SetupTestFunctions(uint64 testReg)
{
auto test = make_convertible<TEST>(testReg);
2019-02-17 22:29:24 -05:00
m_fragmentParams.alphaRef = test.nAlphaRef;
2016-03-11 20:38:17 -05:00
m_validGlState &= ~GLSTATE_FRAGMENT_PARAMS;
m_renderState.depthTest = (test.nDepthEnabled != 0) && m_depthTestingEnabled;
m_validGlState &= ~GLSTATE_DEPTHTEST;
if(test.nDepthEnabled)
{
unsigned int nFunc = GL_NEVER;
switch(test.nDepthMethod)
{
case DEPTH_TEST_NEVER:
nFunc = GL_NEVER;
break;
case DEPTH_TEST_ALWAYS:
nFunc = GL_ALWAYS;
break;
case DEPTH_TEST_GEQUAL:
nFunc = GL_GEQUAL;
break;
case DEPTH_TEST_GREATER:
nFunc = GL_GREATER;
break;
}
glDepthFunc(nFunc);
}
}
void CGSH_OpenGL::SetupDepthBuffer(uint64 zbufReg, uint64 testReg)
{
auto zbuf = make_convertible<ZBUF>(zbufReg);
auto test = make_convertible<TEST>(testReg);
bool depthWriteEnabled = (zbuf.nMask ? false : true);
//If alpha test is enabled for always failing and update only colors, depth writes are disabled
if(
2018-04-30 21:01:23 +01:00
(test.nAlphaEnabled == 1) &&
(test.nAlphaMethod == ALPHA_TEST_NEVER) &&
((test.nAlphaFail == ALPHA_TEST_FAIL_FBONLY) || (test.nAlphaFail == ALPHA_TEST_FAIL_RGBONLY)))
{
depthWriteEnabled = false;
}
2016-03-12 14:20:00 -05:00
m_renderState.depthMask = depthWriteEnabled;
m_validGlState &= ~GLSTATE_DEPTHMASK;
}
void CGSH_OpenGL::SetupFramebuffer(uint64 frameReg, uint64 zbufReg, uint64 scissorReg, uint64 testReg)
{
if(frameReg == 0) return;
auto frame = make_convertible<FRAME>(frameReg);
auto zbuf = make_convertible<ZBUF>(zbufReg);
auto scissor = make_convertible<SCISSOR>(scissorReg);
auto test = make_convertible<TEST>(testReg);
bool r = (frame.nMask & 0x000000FF) == 0;
bool g = (frame.nMask & 0x0000FF00) == 0;
bool b = (frame.nMask & 0x00FF0000) == 0;
bool a = (frame.nMask & 0xFF000000) == 0;
//Don't write to alpha in PSMCT24
if(frame.nPsm == PSMCT24)
{
a = false;
}
2019-08-06 22:08:46 -04:00
if((test.nAlphaEnabled == 1) && (test.nAlphaMethod == ALPHA_TEST_NEVER))
{
if(test.nAlphaFail == ALPHA_TEST_FAIL_RGBONLY)
{
a = false;
}
else if(test.nAlphaFail == ALPHA_TEST_FAIL_ZBONLY)
{
r = g = b = a = false;
}
}
2016-02-24 21:07:55 -05:00
m_renderState.colorMaskR = r;
m_renderState.colorMaskG = g;
m_renderState.colorMaskB = b;
m_renderState.colorMaskA = a;
m_validGlState &= ~GLSTATE_COLORMASK;
//Check if we're drawing into a buffer that's been used for depth before
{
auto zbufWrite = make_convertible<ZBUF>(frameReg);
auto depthbuffer = FindDepthbuffer(zbufWrite, frame);
m_drawingToDepth = (depthbuffer != nullptr);
}
//Look for a framebuffer that matches the specified information
auto framebuffer = FindFramebuffer(frame);
if(!framebuffer)
{
framebuffer = FramebufferPtr(new CFramebuffer(frame.GetBasePtr(), frame.GetWidth(), FRAMEBUFFER_HEIGHT, frame.nPsm, m_fbScale, m_multisampleEnabled));
m_framebuffers.push_back(framebuffer);
PopulateFramebuffer(framebuffer);
}
CommitFramebufferDirtyPages(framebuffer, scissor.scay0, scissor.scay1);
auto depthbuffer = FindDepthbuffer(zbuf, frame);
if(!depthbuffer)
{
depthbuffer = DepthbufferPtr(new CDepthbuffer(zbuf.GetBasePtr(), frame.GetWidth(), FRAMEBUFFER_HEIGHT, zbuf.nPsm, m_fbScale, m_multisampleEnabled));
m_depthbuffers.push_back(depthbuffer);
}
assert(framebuffer->m_width == depthbuffer->m_width);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuffer->m_depthBuffer);
CHECKGLERROR();
2021-01-27 16:09:00 +00:00
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
m_renderState.framebufferHandle = framebuffer->m_framebuffer;
2018-04-30 21:01:23 +01:00
m_validGlState |= GLSTATE_FRAMEBUFFER; //glBindFramebuffer used to set just above
2016-02-26 19:12:50 -05:00
//We assume that we will be drawing to this framebuffer and that we'll need
//to resolve samples at some point if multisampling is enabled
framebuffer->m_resolveNeeded = true;
{
GLenum drawBufferId = GL_COLOR_ATTACHMENT0;
glDrawBuffers(1, &drawBufferId);
CHECKGLERROR();
}
2016-03-02 09:47:18 -05:00
m_renderState.viewportWidth = framebuffer->m_width;
m_renderState.viewportHeight = framebuffer->m_height;
m_validGlState &= ~GLSTATE_VIEWPORT;
float projWidth = static_cast<float>(framebuffer->m_width);
float projHeight = static_cast<float>(framebuffer->m_height);
MakeLinearZOrtho(m_vertexParams.projMatrix, 0, projWidth, 0, projHeight);
m_validGlState &= ~GLSTATE_VERTEX_PARAMS;
m_renderState.scissorX = scissor.scax0;
m_renderState.scissorY = scissor.scay0;
m_renderState.scissorWidth = scissor.scax1 - scissor.scax0 + 1;
m_renderState.scissorHeight = scissor.scay1 - scissor.scay0 + 1;
m_validGlState &= ~GLSTATE_SCISSOR;
}
void CGSH_OpenGL::SetupFogColor(uint64 fogColReg)
{
auto fogCol = make_convertible<FOGCOL>(fogColReg);
m_fragmentParams.fogColor[0] = static_cast<float>(fogCol.nFCR) / 255.0f;
m_fragmentParams.fogColor[1] = static_cast<float>(fogCol.nFCG) / 255.0f;
m_fragmentParams.fogColor[2] = static_cast<float>(fogCol.nFCB) / 255.0f;
m_validGlState &= ~GLSTATE_FRAGMENT_PARAMS;
}
bool CGSH_OpenGL::CanRegionRepeatClampModeSimplified(uint32 clampMin, uint32 clampMax)
{
for(unsigned int j = 1; j < 0x3FF; j = ((j << 1) | 1))
{
if(clampMin < j) break;
if(clampMin != j) continue;
if((clampMin & clampMax) != 0) break;
return true;
}
return false;
}
void CGSH_OpenGL::FillShaderCapsFromTexture(SHADERCAPS& shaderCaps, const uint64& tex0Reg, const uint64& tex1Reg, const uint64& texAReg, const uint64& clampReg)
{
auto tex0 = make_convertible<TEX0>(tex0Reg);
auto tex1 = make_convertible<TEX1>(tex1Reg);
auto texA = make_convertible<TEXA>(texAReg);
auto clamp = make_convertible<CLAMP>(clampReg);
shaderCaps.texSourceMode = TEXTURE_SOURCE_MODE_STD;
if((clamp.nWMS >= CLAMP_MODE_CLAMP) || (clamp.nWMT >= CLAMP_MODE_CLAMP))
{
unsigned int clampMode[2];
clampMode[0] = g_shaderClampModes[clamp.nWMS];
clampMode[1] = g_shaderClampModes[clamp.nWMT];
if(clampMode[0] == TEXTURE_CLAMP_MODE_REGION_REPEAT && CanRegionRepeatClampModeSimplified(clamp.GetMinU(), clamp.GetMaxU())) clampMode[0] = TEXTURE_CLAMP_MODE_REGION_REPEAT_SIMPLE;
if(clampMode[1] == TEXTURE_CLAMP_MODE_REGION_REPEAT && CanRegionRepeatClampModeSimplified(clamp.GetMinV(), clamp.GetMaxV())) clampMode[1] = TEXTURE_CLAMP_MODE_REGION_REPEAT_SIMPLE;
shaderCaps.texClampS = clampMode[0];
shaderCaps.texClampT = clampMode[1];
}
if(CGsPixelFormats::IsPsmIDTEX(tex0.nPsm))
{
if((tex1.nMinFilter != MIN_FILTER_NEAREST) || (tex1.nMagFilter != MIN_FILTER_NEAREST))
{
//We'll need to filter the texture manually
shaderCaps.texBilinearFilter = 1;
}
if(m_forceBilinearTextures)
{
shaderCaps.texBilinearFilter = 1;
}
}
if(tex0.nColorComp == 1)
{
shaderCaps.texHasAlpha = 1;
}
if((tex0.nPsm == PSMCT16) || (tex0.nPsm == PSMCT16S) || (tex0.nPsm == PSMCT24))
{
shaderCaps.texUseAlphaExpansion = 1;
}
if(CGsPixelFormats::IsPsmIDTEX(tex0.nPsm))
{
if((tex0.nCPSM == PSMCT16) || (tex0.nCPSM == PSMCT16S))
{
shaderCaps.texUseAlphaExpansion = 1;
}
shaderCaps.texSourceMode = CGsPixelFormats::IsPsmIDTEX4(tex0.nPsm) ? TEXTURE_SOURCE_MODE_IDX4 : TEXTURE_SOURCE_MODE_IDX8;
}
if(texA.nAEM)
{
shaderCaps.texBlackIsTransparent = 1;
}
shaderCaps.texFunction = tex0.nFunction;
}
void CGSH_OpenGL::FillShaderCapsFromTest(SHADERCAPS& shaderCaps, const uint64& testReg)
{
auto test = make_convertible<TEST>(testReg);
if(test.nAlphaEnabled)
{
//Handle special way of turning off color or depth writes
//Proper write masks will be set at other places
if((test.nAlphaMethod == ALPHA_TEST_NEVER) && (test.nAlphaFail != ALPHA_TEST_FAIL_KEEP))
{
shaderCaps.hasAlphaTest = 0;
}
else
{
shaderCaps.hasAlphaTest = m_alphaTestingEnabled ? 1 : 0;
shaderCaps.alphaTestMethod = test.nAlphaMethod;
shaderCaps.alphaFailMethod = test.nAlphaFail;
}
}
else
{
shaderCaps.hasAlphaTest = 0;
}
shaderCaps.hasDestAlphaTest = test.nDestAlphaEnabled;
shaderCaps.destAlphaTestRef = test.nDestAlphaMode;
}
void CGSH_OpenGL::FillShaderCapsFromAlpha(SHADERCAPS& shaderCaps, bool alphaEnabled, const uint64& alphaReg)
2020-06-10 18:34:25 -04:00
{
if(!alphaEnabled) return;
2020-06-10 18:34:25 -04:00
auto alpha = make_convertible<ALPHA>(alphaReg);
if(m_hasFramebufferFetchExtension)
{
shaderCaps.hasAlphaBlend = alphaEnabled;
shaderCaps.alphaBlendA = alpha.nA;
shaderCaps.alphaBlendB = alpha.nB;
shaderCaps.alphaBlendC = alpha.nC;
shaderCaps.alphaBlendD = alpha.nD;
}
else
{
//If we don't use the source color at all, output white to support some blending modes (ex: ones that doubles dest color).
shaderCaps.colorOutputWhite = (alpha.nA != ALPHABLEND_ABD_CS) && (alpha.nB != ALPHABLEND_ABD_CS) && (alpha.nD != ALPHABLEND_ABD_CS);
}
2020-06-10 18:34:25 -04:00
}
void CGSH_OpenGL::SetupTexture(uint64 primReg, uint64 tex0Reg, uint64 tex1Reg, uint64 texAReg, uint64 clampReg)
{
2016-02-24 21:13:53 -05:00
m_renderState.texture0Handle = 0;
m_renderState.texture1Handle = 0;
m_renderState.texture0MinFilter = GL_NEAREST;
m_renderState.texture0MagFilter = GL_NEAREST;
m_renderState.texture0WrapS = GL_CLAMP_TO_EDGE;
m_renderState.texture0WrapT = GL_CLAMP_TO_EDGE;
m_validGlState &= ~GLSTATE_TEXTURE;
auto prim = make_convertible<PRMODE>(primReg);
if(tex0Reg == 0 || prim.nTexture == 0)
{
return;
}
auto tex0 = make_convertible<TEX0>(tex0Reg);
auto tex1 = make_convertible<TEX1>(tex1Reg);
auto texA = make_convertible<TEXA>(texAReg);
auto clamp = make_convertible<CLAMP>(clampReg);
m_nTexWidth = tex0.GetWidth();
m_nTexHeight = tex0.GetHeight();
auto texInfo = PrepareTexture(tex0);
2016-02-24 21:13:53 -05:00
m_renderState.texture0Handle = texInfo.textureHandle;
m_renderState.texture0AlphaAsIndex = texInfo.alphaAsIndex;
//Setup sampling modes
2016-02-24 21:13:53 -05:00
switch(tex1.nMagFilter)
{
2016-02-24 21:13:53 -05:00
case MAG_FILTER_NEAREST:
m_renderState.texture0MagFilter = GL_NEAREST;
break;
case MAG_FILTER_LINEAR:
m_renderState.texture0MagFilter = GL_LINEAR;
break;
}
switch(tex1.nMinFilter)
{
case MIN_FILTER_NEAREST:
case MIN_FILTER_NEAREST_MIP_NEAREST:
case MIN_FILTER_NEAREST_MIP_LINEAR:
2016-02-24 21:13:53 -05:00
m_renderState.texture0MinFilter = GL_NEAREST;
break;
case MIN_FILTER_LINEAR:
case MIN_FILTER_LINEAR_MIP_NEAREST:
case MIN_FILTER_LINEAR_MIP_LINEAR:
m_renderState.texture0MinFilter = GL_LINEAR;
break;
default:
assert(0);
break;
}
2016-01-09 22:04:01 -05:00
if(m_forceBilinearTextures)
{
2016-02-24 21:13:53 -05:00
m_renderState.texture0MagFilter = GL_LINEAR;
m_renderState.texture0MinFilter = GL_LINEAR;
}
2018-04-30 21:01:23 +01:00
unsigned int clampMin[2] = {0, 0};
unsigned int clampMax[2] = {0, 0};
float textureScaleRatio[2] = {texInfo.scaleRatioX, texInfo.scaleRatioY};
2016-02-24 21:13:53 -05:00
m_renderState.texture0WrapS = g_nativeClampModes[clamp.nWMS];
m_renderState.texture0WrapT = g_nativeClampModes[clamp.nWMT];
if((clamp.nWMS >= CLAMP_MODE_CLAMP) || (clamp.nWMT >= CLAMP_MODE_CLAMP))
{
unsigned int clampMode[2];
clampMode[0] = g_shaderClampModes[clamp.nWMS];
clampMode[1] = g_shaderClampModes[clamp.nWMT];
clampMin[0] = clamp.GetMinU();
clampMin[1] = clamp.GetMinV();
clampMax[0] = clamp.GetMaxU();
clampMax[1] = clamp.GetMaxV();
for(unsigned int i = 0; i < 2; i++)
{
if(clampMode[i] == TEXTURE_CLAMP_MODE_CLAMP)
{
uint32 size = (i == 0) ? tex0.GetWidth() : tex0.GetHeight();
clampMin[i] = 0;
clampMax[i] = static_cast<unsigned int>(static_cast<float>(size - 1) * textureScaleRatio[i]);
}
else if(clampMode[i] == TEXTURE_CLAMP_MODE_REGION_REPEAT)
{
if(CanRegionRepeatClampModeSimplified(clampMin[i], clampMax[i]))
{
clampMin[i]++;
}
}
else if(clampMode[i] == TEXTURE_CLAMP_MODE_REGION_CLAMP)
{
clampMin[i] = static_cast<unsigned int>(static_cast<float>(clampMin[i]) * textureScaleRatio[i]);
clampMax[i] = static_cast<unsigned int>(static_cast<float>(clampMax[i]) * textureScaleRatio[i]);
}
}
}
2018-04-30 21:01:23 +01:00
if(CGsPixelFormats::IsPsmIDTEX(tex0.nPsm) &&
(m_renderState.texture0MinFilter != GL_NEAREST || m_renderState.texture0MagFilter != GL_NEAREST))
{
//We'll need to filter the texture manually
2016-02-24 21:13:53 -05:00
m_renderState.texture0MinFilter = GL_NEAREST;
m_renderState.texture0MagFilter = GL_NEAREST;
}
if(CGsPixelFormats::IsPsmIDTEX(tex0.nPsm))
{
2016-02-24 21:13:53 -05:00
m_renderState.texture1Handle = PreparePalette(tex0);
}
memset(m_vertexParams.texMatrix, 0, sizeof(m_vertexParams.texMatrix));
m_vertexParams.texMatrix[0 + (0 * 4)] = texInfo.scaleRatioX;
m_vertexParams.texMatrix[1 + (1 * 4)] = texInfo.scaleRatioY;
m_vertexParams.texMatrix[2 + (2 * 4)] = 1;
m_vertexParams.texMatrix[0 + (3 * 4)] = texInfo.offsetX;
m_vertexParams.texMatrix[3 + (3 * 4)] = 1;
m_validGlState &= ~GLSTATE_VERTEX_PARAMS;
m_fragmentParams.textureSize[0] = static_cast<float>(tex0.GetWidth());
m_fragmentParams.textureSize[1] = static_cast<float>(tex0.GetHeight());
m_fragmentParams.texelSize[0] = 1.0f / static_cast<float>(tex0.GetWidth());
m_fragmentParams.texelSize[1] = 1.0f / static_cast<float>(tex0.GetHeight());
m_fragmentParams.clampMin[0] = static_cast<float>(clampMin[0]);
m_fragmentParams.clampMin[1] = static_cast<float>(clampMin[1]);
m_fragmentParams.clampMax[0] = static_cast<float>(clampMax[0]);
m_fragmentParams.clampMax[1] = static_cast<float>(clampMax[1]);
m_fragmentParams.texA0 = static_cast<float>(texA.nTA0) / 255.f;
m_fragmentParams.texA1 = static_cast<float>(texA.nTA1) / 255.f;
m_validGlState &= ~GLSTATE_FRAGMENT_PARAMS;
}
CGSH_OpenGL::FramebufferPtr CGSH_OpenGL::FindFramebuffer(const FRAME& frame) const
{
2018-04-30 21:01:23 +01:00
auto framebufferIterator = std::find_if(std::begin(m_framebuffers), std::end(m_framebuffers),
[&](const FramebufferPtr& framebuffer) {
return (framebuffer->m_basePtr == frame.GetBasePtr()) &&
IsCompatibleFramebufferPSM(framebuffer->m_psm, frame.nPsm) &&
2018-04-30 21:01:23 +01:00
(framebuffer->m_width == frame.GetWidth());
});
return (framebufferIterator != std::end(m_framebuffers)) ? *(framebufferIterator) : FramebufferPtr();
}
CGSH_OpenGL::DepthbufferPtr CGSH_OpenGL::FindDepthbuffer(const ZBUF& zbuf, const FRAME& frame) const
{
2018-04-30 21:01:23 +01:00
auto depthbufferIterator = std::find_if(std::begin(m_depthbuffers), std::end(m_depthbuffers),
[&](const DepthbufferPtr& depthbuffer) {
return (depthbuffer->m_basePtr == zbuf.GetBasePtr()) && (depthbuffer->m_width == frame.GetWidth());
});
return (depthbufferIterator != std::end(m_depthbuffers)) ? *(depthbufferIterator) : DepthbufferPtr();
}
/////////////////////////////////////////////////////////////
// Individual Primitives Implementations
/////////////////////////////////////////////////////////////
void CGSH_OpenGL::Prim_Point()
{
2021-08-19 21:01:17 -04:00
auto xyz = make_convertible<XYZ>(m_VtxBuffer[0].position);
auto rgbaq = make_convertible<RGBAQ>(m_VtxBuffer[0].rgbaq);
2015-12-12 22:13:12 -05:00
2018-04-30 21:01:23 +01:00
float x = xyz.GetX();
float y = xyz.GetY();
uint32 z = xyz.nZ;
2015-12-12 22:13:12 -05:00
x -= m_nPrimOfsX;
y -= m_nPrimOfsY;
auto color = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq.nR, rgbaq.nG,
rgbaq.nB, rgbaq.nA);
2015-12-12 22:13:12 -05:00
2018-04-30 12:00:43 -04:00
// clang-format off
2015-12-12 22:13:12 -05:00
PRIM_VERTEX vertex =
{
//x, y, z, color, s, t, q, f
x, y, z, color, 0, 0, 1, 0,
};
2018-04-30 12:00:43 -04:00
// clang-format on
2015-12-12 22:13:12 -05:00
m_vertexBuffer.push_back(vertex);
}
void CGSH_OpenGL::Prim_Line()
{
XYZ xyz[2];
2021-08-19 21:01:17 -04:00
xyz[0] <<= m_VtxBuffer[1].position;
xyz[1] <<= m_VtxBuffer[0].position;
2018-04-30 21:01:23 +01:00
float nX1 = xyz[0].GetX();
float nY1 = xyz[0].GetY();
uint32 nZ1 = xyz[0].nZ;
2018-04-30 21:01:23 +01:00
float nX2 = xyz[1].GetX();
float nY2 = xyz[1].GetY();
uint32 nZ2 = xyz[1].nZ;
nX1 -= m_nPrimOfsX;
nX2 -= m_nPrimOfsX;
nY1 -= m_nPrimOfsY;
nY2 -= m_nPrimOfsY;
RGBAQ rgbaq[2];
2021-08-19 21:01:17 -04:00
rgbaq[0] <<= m_VtxBuffer[1].rgbaq;
rgbaq[1] <<= m_VtxBuffer[0].rgbaq;
2018-04-30 21:01:23 +01:00
float nS[2] = {0, 0};
float nT[2] = {0, 0};
float nQ[2] = {1, 1};
2021-07-14 11:15:45 -04:00
if(m_PrimitiveMode.nTexture)
{
if(m_PrimitiveMode.nUseUV)
{
UV uv[2];
2021-08-19 21:01:17 -04:00
uv[0] <<= m_VtxBuffer[1].uv;
uv[1] <<= m_VtxBuffer[0].uv;
2021-07-14 11:15:45 -04:00
nS[0] = uv[0].GetU() / static_cast<float>(m_nTexWidth);
nS[1] = uv[1].GetU() / static_cast<float>(m_nTexWidth);
nT[0] = uv[0].GetV() / static_cast<float>(m_nTexHeight);
nT[1] = uv[1].GetV() / static_cast<float>(m_nTexHeight);
}
else
{
ST st[2];
2021-08-19 21:01:17 -04:00
st[0] <<= m_VtxBuffer[1].st;
st[1] <<= m_VtxBuffer[0].st;
2021-07-14 11:15:45 -04:00
nS[0] = st[0].nS;
nS[1] = st[1].nS;
nT[0] = st[0].nT;
nT[1] = st[1].nT;
bool isQ0Neg = (rgbaq[0].nQ < 0);
bool isQ1Neg = (rgbaq[1].nQ < 0);
assert(isQ0Neg == isQ1Neg);
nQ[0] = rgbaq[0].nQ;
nQ[1] = rgbaq[1].nQ;
}
}
auto color1 = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[0].nR, rgbaq[0].nG,
rgbaq[0].nB, rgbaq[0].nA);
auto color2 = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[1].nR, rgbaq[1].nG,
rgbaq[1].nB, rgbaq[1].nA);
2018-04-30 12:00:43 -04:00
// clang-format off
PRIM_VERTEX vertices[] =
{
{ nX1, nY1, nZ1, color1, nS[0], nT[0], nQ[0], 0 },
{ nX2, nY2, nZ2, color2, nS[1], nT[1], nQ[1], 0 },
};
2018-04-30 12:00:43 -04:00
// clang-format on
m_vertexBuffer.insert(m_vertexBuffer.end(), std::begin(vertices), std::end(vertices));
}
void CGSH_OpenGL::Prim_Triangle()
{
float nF1, nF2, nF3;
XYZ vertex[3];
2021-08-19 21:01:17 -04:00
vertex[0] <<= m_VtxBuffer[2].position;
vertex[1] <<= m_VtxBuffer[1].position;
vertex[2] <<= m_VtxBuffer[0].position;
float nX1 = vertex[0].GetX(), nX2 = vertex[1].GetX(), nX3 = vertex[2].GetX();
float nY1 = vertex[0].GetY(), nY2 = vertex[1].GetY(), nY3 = vertex[2].GetY();
uint32 nZ1 = vertex[0].nZ, nZ2 = vertex[1].nZ, nZ3 = vertex[2].nZ;
2016-08-12 21:09:31 -04:00
RGBAQ rgbaq[3];
2021-08-19 21:01:17 -04:00
rgbaq[0] <<= m_VtxBuffer[2].rgbaq;
rgbaq[1] <<= m_VtxBuffer[1].rgbaq;
rgbaq[2] <<= m_VtxBuffer[0].rgbaq;
nX1 -= m_nPrimOfsX;
nX2 -= m_nPrimOfsX;
nX3 -= m_nPrimOfsX;
nY1 -= m_nPrimOfsY;
nY2 -= m_nPrimOfsY;
nY3 -= m_nPrimOfsY;
if(m_PrimitiveMode.nFog)
{
2021-08-19 21:01:17 -04:00
nF1 = (float)(0xFF - m_VtxBuffer[2].fog) / 255.0f;
nF2 = (float)(0xFF - m_VtxBuffer[1].fog) / 255.0f;
nF3 = (float)(0xFF - m_VtxBuffer[0].fog) / 255.0f;
}
else
{
nF1 = nF2 = nF3 = 0.0;
}
2018-04-30 21:01:23 +01:00
float nS[3] = {0, 0, 0};
float nT[3] = {0, 0, 0};
float nQ[3] = {1, 1, 1};
if(m_PrimitiveMode.nTexture)
{
if(m_PrimitiveMode.nUseUV)
{
UV uv[3];
2021-08-19 21:01:17 -04:00
uv[0] <<= m_VtxBuffer[2].uv;
uv[1] <<= m_VtxBuffer[1].uv;
uv[2] <<= m_VtxBuffer[0].uv;
nS[0] = uv[0].GetU() / static_cast<float>(m_nTexWidth);
nS[1] = uv[1].GetU() / static_cast<float>(m_nTexWidth);
nS[2] = uv[2].GetU() / static_cast<float>(m_nTexWidth);
nT[0] = uv[0].GetV() / static_cast<float>(m_nTexHeight);
nT[1] = uv[1].GetV() / static_cast<float>(m_nTexHeight);
nT[2] = uv[2].GetV() / static_cast<float>(m_nTexHeight);
}
else
{
ST st[3];
2021-08-19 21:01:17 -04:00
st[0] <<= m_VtxBuffer[2].st;
st[1] <<= m_VtxBuffer[1].st;
st[2] <<= m_VtxBuffer[0].st;
2018-04-30 21:01:23 +01:00
nS[0] = st[0].nS;
nS[1] = st[1].nS;
nS[2] = st[2].nS;
nT[0] = st[0].nT;
nT[1] = st[1].nT;
nT[2] = st[2].nT;
bool isQ0Neg = (rgbaq[0].nQ < 0);
bool isQ1Neg = (rgbaq[1].nQ < 0);
bool isQ2Neg = (rgbaq[2].nQ < 0);
assert(isQ0Neg == isQ1Neg);
assert(isQ1Neg == isQ2Neg);
2018-04-30 21:01:23 +01:00
nQ[0] = rgbaq[0].nQ;
nQ[1] = rgbaq[1].nQ;
nQ[2] = rgbaq[2].nQ;
}
}
auto color1 = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[0].nR, rgbaq[0].nG,
rgbaq[0].nB, rgbaq[0].nA);
auto color2 = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[1].nR, rgbaq[1].nG,
rgbaq[1].nB, rgbaq[1].nA);
auto color3 = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[2].nR, rgbaq[2].nG,
rgbaq[2].nB, rgbaq[2].nA);
if(m_PrimitiveMode.nShading == 0)
{
//Flat shaded triangles use the last color set
color1 = color2 = color3;
}
2018-04-30 12:00:43 -04:00
// clang-format off
PRIM_VERTEX vertices[] =
{
{ nX1, nY1, nZ1, color1, nS[0], nT[0], nQ[0], nF1 },
{ nX2, nY2, nZ2, color2, nS[1], nT[1], nQ[1], nF2 },
{ nX3, nY3, nZ3, color3, nS[2], nT[2], nQ[2], nF3 },
};
2018-04-30 12:00:43 -04:00
// clang-format on
m_vertexBuffer.insert(m_vertexBuffer.end(), std::begin(vertices), std::end(vertices));
}
void CGSH_OpenGL::Prim_Sprite()
{
XYZ xyz[2];
2021-08-19 21:01:17 -04:00
xyz[0] <<= m_VtxBuffer[1].position;
xyz[1] <<= m_VtxBuffer[0].position;
2018-04-30 21:01:23 +01:00
float nX1 = xyz[0].GetX();
float nY1 = xyz[0].GetY();
float nX2 = xyz[1].GetX();
float nY2 = xyz[1].GetY();
uint32 nZ = xyz[1].nZ;
2016-08-12 21:09:31 -04:00
RGBAQ rgbaq[2];
2021-08-19 21:01:17 -04:00
rgbaq[0] <<= m_VtxBuffer[1].rgbaq;
rgbaq[1] <<= m_VtxBuffer[0].rgbaq;
nX1 -= m_nPrimOfsX;
nX2 -= m_nPrimOfsX;
nY1 -= m_nPrimOfsY;
nY2 -= m_nPrimOfsY;
2018-04-30 21:01:23 +01:00
float nS[2] = {0, 0};
float nT[2] = {0, 0};
float nF = 0;
if(m_PrimitiveMode.nFog)
{
nF = (float)(0xFF - m_VtxBuffer[0].fog) / 255.0f;
}
if(m_PrimitiveMode.nTexture)
{
if(m_PrimitiveMode.nUseUV)
{
UV uv[2];
2021-08-19 21:01:17 -04:00
uv[0] <<= m_VtxBuffer[1].uv;
uv[1] <<= m_VtxBuffer[0].uv;
nS[0] = uv[0].GetU() / static_cast<float>(m_nTexWidth);
nS[1] = uv[1].GetU() / static_cast<float>(m_nTexWidth);
nT[0] = uv[0].GetV() / static_cast<float>(m_nTexHeight);
nT[1] = uv[1].GetV() / static_cast<float>(m_nTexHeight);
}
else
{
ST st[2];
2021-08-19 21:01:17 -04:00
st[0] <<= m_VtxBuffer[1].st;
st[1] <<= m_VtxBuffer[0].st;
float nQ1 = rgbaq[1].nQ;
float nQ2 = rgbaq[0].nQ;
if(nQ1 == 0) nQ1 = 1;
if(nQ2 == 0) nQ2 = 1;
nS[0] = st[0].nS / nQ1;
nS[1] = st[1].nS / nQ2;
nT[0] = st[0].nT / nQ1;
nT[1] = st[1].nT / nQ2;
}
}
auto color = MakeColor(
2018-04-30 21:01:23 +01:00
rgbaq[1].nR, rgbaq[1].nG,
rgbaq[1].nB, rgbaq[1].nA);
// clang-format off
PRIM_VERTEX vertices[] =
{
{nX1, nY1, nZ, color, nS[0], nT[0], 1, nF},
{nX2, nY1, nZ, color, nS[1], nT[0], 1, nF},
{nX1, nY2, nZ, color, nS[0], nT[1], 1, nF},
{nX1, nY2, nZ, color, nS[0], nT[1], 1, nF},
{nX2, nY1, nZ, color, nS[1], nT[0], 1, nF},
{nX2, nY2, nZ, color, nS[1], nT[1], 1, nF},
};
// clang-format on
m_vertexBuffer.insert(m_vertexBuffer.end(), std::begin(vertices), std::end(vertices));
}
void CGSH_OpenGL::FlushVertexBuffer()
{
if(m_vertexBuffer.empty()) return;
assert(m_renderState.isValid == true);
2021-04-15 13:12:23 -04:00
auto shader = GetShaderFromCaps(m_renderState.shaderCaps);
if(*shader != m_renderState.shaderHandle)
{
2021-04-15 13:12:23 -04:00
m_renderState.shaderHandle = *shader;
m_validGlState &= ~GLSTATE_PROGRAM;
}
2021-04-15 13:12:23 -04:00
DoRenderPass();
m_vertexBuffer.clear();
}
void CGSH_OpenGL::DoRenderPass()
{
if((m_validGlState & GLSTATE_VERTEX_PARAMS) == 0)
{
glBindBuffer(GL_UNIFORM_BUFFER, m_vertexParamsBuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(VERTEXPARAMS), &m_vertexParams, GL_STREAM_DRAW);
CHECKGLERROR();
m_validGlState |= GLSTATE_VERTEX_PARAMS;
}
if((m_validGlState & GLSTATE_FRAGMENT_PARAMS) == 0)
{
glBindBuffer(GL_UNIFORM_BUFFER, m_fragmentParamsBuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(FRAGMENTPARAMS), &m_fragmentParams, GL_STREAM_DRAW);
CHECKGLERROR();
m_validGlState |= GLSTATE_FRAGMENT_PARAMS;
}
if((m_validGlState & GLSTATE_PROGRAM) == 0)
{
glUseProgram(m_renderState.shaderHandle);
m_validGlState |= GLSTATE_PROGRAM;
}
2016-03-02 09:47:18 -05:00
if((m_validGlState & GLSTATE_VIEWPORT) == 0)
{
glViewport(0, 0, m_renderState.viewportWidth * m_fbScale, m_renderState.viewportHeight * m_fbScale);
m_validGlState |= GLSTATE_VIEWPORT;
}
if((m_validGlState & GLSTATE_SCISSOR) == 0)
{
glEnable(GL_SCISSOR_TEST);
glScissor(m_renderState.scissorX * m_fbScale, m_renderState.scissorY * m_fbScale,
2018-04-30 21:01:23 +01:00
m_renderState.scissorWidth * m_fbScale, m_renderState.scissorHeight * m_fbScale);
m_validGlState |= GLSTATE_SCISSOR;
}
if((m_validGlState & GLSTATE_BLEND) == 0)
{
m_renderState.blendEnabled ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
m_validGlState |= GLSTATE_BLEND;
}
if((m_validGlState & GLSTATE_DEPTHTEST) == 0)
{
m_renderState.depthTest ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST);
m_validGlState |= GLSTATE_DEPTHTEST;
}
2016-02-24 21:07:55 -05:00
if((m_validGlState & GLSTATE_COLORMASK) == 0)
{
glColorMask(
2018-04-30 21:01:23 +01:00
m_renderState.colorMaskR, m_renderState.colorMaskG,
m_renderState.colorMaskB, m_renderState.colorMaskA);
2016-02-24 21:07:55 -05:00
m_validGlState |= GLSTATE_COLORMASK;
}
2016-03-12 14:20:00 -05:00
if((m_validGlState & GLSTATE_DEPTHMASK) == 0)
{
glDepthMask(m_renderState.depthMask ? GL_TRUE : GL_FALSE);
m_validGlState |= GLSTATE_DEPTHMASK;
}
2016-02-24 21:13:53 -05:00
if((m_validGlState & GLSTATE_TEXTURE) == 0)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_renderState.texture0Handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_renderState.texture0MinFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_renderState.texture0MagFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_renderState.texture0WrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_renderState.texture0WrapT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, m_renderState.texture0AlphaAsIndex ? GL_ALPHA : GL_RED);
2016-02-24 21:13:53 -05:00
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_renderState.texture1Handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
m_validGlState |= GLSTATE_TEXTURE;
}
if((m_validGlState & GLSTATE_FRAMEBUFFER) == 0)
{
glBindFramebuffer(GL_FRAMEBUFFER, m_renderState.framebufferHandle);
m_validGlState |= GLSTATE_FRAMEBUFFER;
}
glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_vertexParamsBuffer);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, m_fragmentParamsBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_primBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(PRIM_VERTEX) * m_vertexBuffer.size(), m_vertexBuffer.data(), GL_STREAM_DRAW);
glBindVertexArray(m_primVertexArray);
GLenum primitiveMode = GL_NONE;
switch(m_primitiveType)
{
2015-12-12 22:13:12 -05:00
case PRIM_POINT:
primitiveMode = GL_POINTS;
break;
case PRIM_LINE:
case PRIM_LINESTRIP:
primitiveMode = GL_LINES;
break;
case PRIM_TRIANGLE:
case PRIM_TRIANGLESTRIP:
case PRIM_TRIANGLEFAN:
case PRIM_SPRITE:
primitiveMode = GL_TRIANGLES;
break;
default:
assert(false);
break;
}
glDrawArrays(primitiveMode, 0, m_vertexBuffer.size());
m_drawCallCount++;
}
void CGSH_OpenGL::DrawToDepth(unsigned int primitiveType, uint64 primReg)
{
//A game might be attempting to clear depth by using the zbuffer
//as a frame buffer and drawing a black sprite into it
//Space Harrier does this
//Must be flat, no texture map, no fog, no blend and no aa
if((primReg & 0x1F8) != 0) return;
//Must be a sprite
if(primitiveType != PRIM_SPRITE) return;
//Invalidate state
FlushVertexBuffer();
m_renderState.isValid = false;
auto prim = make_convertible<PRMODE>(primReg);
uint64 frameReg = m_nReg[GS_REG_FRAME_1 + prim.nContext];
auto frame = make_convertible<FRAME>(frameReg);
auto zbufWrite = make_convertible<ZBUF>(frameReg);
auto depthbuffer = FindDepthbuffer(zbufWrite, frame);
assert(depthbuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuffer->m_depthBuffer);
CHECKGLERROR();
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
glDepthMask(GL_TRUE);
glClearDepthf(0);
glClear(GL_DEPTH_BUFFER_BIT);
2016-03-12 14:20:00 -05:00
m_validGlState &= ~GLSTATE_DEPTHMASK;
}
void CGSH_OpenGL::CopyToFb(
2018-04-30 21:01:23 +01:00
int32 srcX0, int32 srcY0, int32 srcX1, int32 srcY1,
int32 srcWidth, int32 srcHeight,
int32 dstX0, int32 dstY0, int32 dstX1, int32 dstY1)
{
m_validGlState &= ~(GLSTATE_BLEND | GLSTATE_COLORMASK | GLSTATE_SCISSOR | GLSTATE_PROGRAM);
m_validGlState &= ~(GLSTATE_VIEWPORT | GLSTATE_DEPTHTEST | GLSTATE_DEPTHMASK);
assert(srcX1 >= srcX0);
assert(srcY1 >= srcY0);
assert(dstX1 >= dstX0);
assert(dstY1 >= dstY0);
float positionX = static_cast<float>(srcX0) / static_cast<float>(srcWidth);
float positionY = static_cast<float>(srcY0) / static_cast<float>(srcHeight);
float sizeX = static_cast<float>(srcX1 - srcX0) / static_cast<float>(srcWidth);
float sizeY = static_cast<float>(srcY1 - srcY0) / static_cast<float>(srcHeight);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_SCISSOR_TEST);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_FALSE);
glUseProgram(*m_copyToFbProgram);
glUniform2f(m_copyToFbSrcPositionUniform, positionX, positionY);
glUniform2f(m_copyToFbSrcSizeUniform, sizeX, sizeY);
glViewport(dstX0, dstY0, dstX1 - dstX0, dstY1 - dstY0);
glBindBuffer(GL_ARRAY_BUFFER, m_copyToFbVertexBuffer);
glBindVertexArray(m_copyToFbVertexArray);
#ifdef _DEBUG
m_copyToFbProgram->Validate();
#endif
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
CHECKGLERROR();
}
/////////////////////////////////////////////////////////////
// Other Functions
/////////////////////////////////////////////////////////////
void CGSH_OpenGL::WriteRegisterImpl(uint8 nRegister, uint64 nData)
{
CGSHandler::WriteRegisterImpl(nRegister, nData);
switch(nRegister)
{
case GS_REG_PRIM:
m_pendingPrim = true;
m_pendingPrimValue = nData;
break;
case GS_REG_XYZ2:
case GS_REG_XYZ3:
case GS_REG_XYZF2:
case GS_REG_XYZF3:
VertexKick(nRegister, nData);
break;
}
}
void CGSH_OpenGL::ProcessPrim(uint64 value)
{
unsigned int newPrimitiveType = static_cast<unsigned int>(value & 0x07);
if(newPrimitiveType != m_primitiveType)
{
FlushVertexBuffer();
}
m_primitiveType = newPrimitiveType;
switch(m_primitiveType)
{
case PRIM_POINT:
m_nVtxCount = 1;
break;
case PRIM_LINE:
case PRIM_LINESTRIP:
m_nVtxCount = 2;
break;
case PRIM_TRIANGLE:
case PRIM_TRIANGLESTRIP:
case PRIM_TRIANGLEFAN:
m_nVtxCount = 3;
break;
case PRIM_SPRITE:
m_nVtxCount = 2;
break;
}
}
void CGSH_OpenGL::VertexKick(uint8 nRegister, uint64 nValue)
{
if(m_pendingPrim)
{
m_pendingPrim = false;
ProcessPrim(m_pendingPrimValue);
}
if(m_nVtxCount == 0) return;
bool nDrawingKick = (nRegister == GS_REG_XYZ2) || (nRegister == GS_REG_XYZF2);
bool nFog = (nRegister == GS_REG_XYZF2) || (nRegister == GS_REG_XYZF3);
if(!m_drawEnabled) nDrawingKick = false;
if(nFog)
{
2021-08-19 21:01:17 -04:00
m_VtxBuffer[m_nVtxCount - 1].position = nValue & 0x00FFFFFFFFFFFFFFULL;
m_VtxBuffer[m_nVtxCount - 1].rgbaq = m_nReg[GS_REG_RGBAQ];
m_VtxBuffer[m_nVtxCount - 1].uv = m_nReg[GS_REG_UV];
m_VtxBuffer[m_nVtxCount - 1].st = m_nReg[GS_REG_ST];
m_VtxBuffer[m_nVtxCount - 1].fog = (uint8)(nValue >> 56);
}
else
{
2021-08-19 21:01:17 -04:00
m_VtxBuffer[m_nVtxCount - 1].position = nValue;
m_VtxBuffer[m_nVtxCount - 1].rgbaq = m_nReg[GS_REG_RGBAQ];
m_VtxBuffer[m_nVtxCount - 1].uv = m_nReg[GS_REG_UV];
m_VtxBuffer[m_nVtxCount - 1].st = m_nReg[GS_REG_ST];
m_VtxBuffer[m_nVtxCount - 1].fog = (uint8)(m_nReg[GS_REG_FOG] >> 56);
}
m_nVtxCount--;
if(m_nVtxCount == 0)
{
if((m_nReg[GS_REG_PRMODECONT] & 1) != 0)
{
m_PrimitiveMode <<= m_nReg[GS_REG_PRIM];
}
else
{
m_PrimitiveMode <<= m_nReg[GS_REG_PRMODE];
}
if(nDrawingKick)
{
SetRenderingContext(m_PrimitiveMode);
}
switch(m_primitiveType)
{
case PRIM_POINT:
if(nDrawingKick) Prim_Point();
m_nVtxCount = 1;
break;
case PRIM_LINE:
if(nDrawingKick) Prim_Line();
m_nVtxCount = 2;
break;
case PRIM_LINESTRIP:
if(nDrawingKick) Prim_Line();
memcpy(&m_VtxBuffer[1], &m_VtxBuffer[0], sizeof(VERTEX));
m_nVtxCount = 1;
break;
case PRIM_TRIANGLE:
if(nDrawingKick) Prim_Triangle();
m_nVtxCount = 3;
break;
case PRIM_TRIANGLESTRIP:
if(nDrawingKick) Prim_Triangle();
memcpy(&m_VtxBuffer[2], &m_VtxBuffer[1], sizeof(VERTEX));
memcpy(&m_VtxBuffer[1], &m_VtxBuffer[0], sizeof(VERTEX));
m_nVtxCount = 1;
break;
case PRIM_TRIANGLEFAN:
if(nDrawingKick) Prim_Triangle();
memcpy(&m_VtxBuffer[1], &m_VtxBuffer[0], sizeof(VERTEX));
m_nVtxCount = 1;
break;
case PRIM_SPRITE:
if(nDrawingKick) Prim_Sprite();
m_nVtxCount = 2;
break;
}
if(nDrawingKick && m_drawingToDepth)
{
DrawToDepth(m_primitiveType, m_PrimitiveMode);
}
}
}
void CGSH_OpenGL::ProcessHostToLocalTransfer()
{
if(m_trxCtx.nDirty)
{
FlushVertexBuffer();
m_renderState.isTextureStateValid = false;
m_renderState.isFramebufferStateValid = false;
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
auto [transferAddress, transferSize] = GsTransfer::GetDstRange(bltBuf, trxReg, trxPos);
m_textureCache.InvalidateRange(transferAddress, transferSize);
bool isUpperByteTransfer = (bltBuf.nDstPsm == PSMT8H) || (bltBuf.nDstPsm == PSMT4HL) || (bltBuf.nDstPsm == PSMT4HH);
for(const auto& framebuffer : m_framebuffers)
{
if((framebuffer->m_psm == PSMCT24) && isUpperByteTransfer) continue;
framebuffer->m_cachedArea.Invalidate(transferAddress, transferSize);
}
}
}
void CGSH_OpenGL::ProcessLocalToHostTransfer()
{
//This is constrained to work only with ps2autotest, will be unconstrained later
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
if(bltBuf.nSrcPsm != PSMCT32) return;
uint32 transferAddress = bltBuf.GetSrcPtr();
if(transferAddress != 0) return;
if((trxReg.nRRW != 32) || (trxReg.nRRH != 32)) return;
if((trxPos.nSSAX != 0) || (trxPos.nSSAY != 0)) return;
2018-04-30 21:01:23 +01:00
auto framebufferIterator = std::find_if(m_framebuffers.begin(), m_framebuffers.end(),
[](const FramebufferPtr& framebuffer) {
return (framebuffer->m_psm == PSMCT32) && (framebuffer->m_basePtr == 0);
});
if(framebufferIterator == std::end(m_framebuffers)) return;
const auto& framebuffer = (*framebufferIterator);
FlushVertexBuffer();
m_renderState.isValid = false;
auto pixels = new uint32[trxReg.nRRW * trxReg.nRRH];
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
glReadPixels(trxPos.nSSAX, trxPos.nSSAY, trxReg.nRRW, trxReg.nRRH, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
CHECKGLERROR();
//Write back to RAM
{
CGsPixelFormats::CPixelIndexorPSMCT32 indexor(m_pRAM, bltBuf.GetSrcPtr(), bltBuf.nSrcWidth);
for(uint32 y = trxPos.nSSAY; y < (trxPos.nSSAY + trxReg.nRRH); y++)
{
for(uint32 x = trxPos.nSSAX; x < (trxPos.nSSAX + trxReg.nRRH); x++)
{
uint32 pixel = pixels[x + (y * trxReg.nRRW)];
indexor.SetPixel(x, y, pixel);
}
}
}
2018-04-30 21:01:23 +01:00
delete[] pixels;
}
void CGSH_OpenGL::ProcessLocalToLocalTransfer()
{
auto bltBuf = make_convertible<BITBLTBUF>(m_nReg[GS_REG_BITBLTBUF]);
2018-04-30 21:01:23 +01:00
auto srcFramebufferIterator = std::find_if(m_framebuffers.begin(), m_framebuffers.end(),
[&](const FramebufferPtr& framebuffer) {
return (framebuffer->m_basePtr == bltBuf.GetSrcPtr()) &&
(framebuffer->m_width == bltBuf.GetSrcWidth());
});
auto dstFramebufferIterator = std::find_if(m_framebuffers.begin(), m_framebuffers.end(),
[&](const FramebufferPtr& framebuffer) {
return (framebuffer->m_basePtr == bltBuf.GetDstPtr()) &&
(framebuffer->m_width == bltBuf.GetDstWidth());
});
bool foundSrc = srcFramebufferIterator != std::end(m_framebuffers);
bool foundDest = dstFramebufferIterator != std::end(m_framebuffers);
if(foundSrc && foundDest)
{
FlushVertexBuffer();
m_renderState.isValid = false;
const auto& srcFramebuffer = (*srcFramebufferIterator);
const auto& dstFramebuffer = (*dstFramebufferIterator);
glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer->m_framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFramebuffer->m_framebuffer);
//Copy buffers
glBlitFramebuffer(
2018-04-30 21:01:23 +01:00
0, 0, srcFramebuffer->m_width * m_fbScale, srcFramebuffer->m_height * m_fbScale,
0, 0, srcFramebuffer->m_width * m_fbScale, srcFramebuffer->m_height * m_fbScale,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
CHECKGLERROR();
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
else if(foundSrc && !foundDest)
{
FlushVertexBuffer();
m_renderState.isValid = false;
auto trxPos = make_convertible<TRXPOS>(m_nReg[GS_REG_TRXPOS]);
auto trxReg = make_convertible<TRXREG>(m_nReg[GS_REG_TRXREG]);
auto imgbuffer = Framework::CBitmap(trxReg.nRRW * m_fbScale, trxReg.nRRH * m_fbScale, 32);
glBindFramebuffer(GL_FRAMEBUFFER, (*srcFramebufferIterator)->m_framebuffer);
glReadPixels(trxPos.nSSAX * m_fbScale, trxPos.nSSAY * m_fbScale, trxReg.nRRW * m_fbScale, trxReg.nRRH * m_fbScale, GL_RGBA, GL_UNSIGNED_BYTE, imgbuffer.GetPixels());
CHECKGLERROR();
// Sample down to requested size
imgbuffer = imgbuffer.Resize(trxReg.nRRW, trxReg.nRRH);
auto [transferAddress, transferSize] = GsTransfer::GetDstRange(bltBuf, trxReg, trxPos);
m_textureCache.InvalidateRange(transferAddress, transferSize);
//Write back to RAM
{
CGsPixelFormats::CPixelIndexorPSMCT32 indexor(m_pRAM, bltBuf.GetDstPtr(), bltBuf.nDstWidth);
for(uint32 y = 0; y < trxReg.nRRH; y++)
{
for(uint32 x = 0; x < trxReg.nRRW; x++)
{
auto pixel = imgbuffer.GetPixel(x, y);
indexor.SetPixel(trxPos.nDSAX + x, trxPos.nDSAY + y, MakeColor(pixel.r, pixel.g, pixel.b, pixel.a));
}
}
}
}
else
{
assert(0);
}
}
void CGSH_OpenGL::ProcessClutTransfer(uint32 csa, uint32)
{
FlushVertexBuffer();
m_renderState.isTextureStateValid = false;
PalCache_Invalidate(csa);
}
2017-04-04 21:42:28 +01:00
Framework::CBitmap CGSH_OpenGL::GetScreenshot()
{
#if 0
2020-01-25 22:37:13 +00:00
auto dispInfo = GetCurrentDisplayInfo();
const auto& dispLayer = dispInfo.layers[0];
FramebufferPtr framebuffer;
for(const auto& candidateFramebuffer : m_framebuffers)
{
if(
(candidateFramebuffer->m_basePtr == dispLayer.bufPtr) &&
(GetFramebufferBitDepth(candidateFramebuffer->m_psm) == GetFramebufferBitDepth(dispLayer.psm)) &&
(candidateFramebuffer->m_width == dispLayer.bufWidth))
{
//We have a winner
framebuffer = candidateFramebuffer;
break;
}
}
auto imgbuffer = Framework::CBitmap(dispWidth * m_fbScale, dispHeight * m_fbScale, 32);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
glReadPixels(0, 0, dispWidth * m_fbScale, dispHeight * m_fbScale, GL_RGBA, GL_UNSIGNED_BYTE, imgbuffer.GetPixels());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if(halfHeight)
{
return imgbuffer.Resize(dispWidth * m_fbScale, dispHeight * 2 * m_fbScale);
}
return imgbuffer;
#endif
return Framework::CBitmap();
2017-04-04 21:42:28 +01:00
}
Framework::CBitmap CGSH_OpenGL::GetFramebuffer(uint64 frameReg)
{
Framework::CBitmap result;
2020-01-26 12:40:17 +00:00
SendGSCall([&]() { result = GetFramebufferImpl(frameReg); }, true);
return result;
}
Framework::CBitmap CGSH_OpenGL::GetFramebufferImpl(uint64 frameReg)
{
#ifndef GLES_COMPATIBILITY
auto frame = make_convertible<FRAME>(frameReg);
auto framebuffer = FindFramebuffer(frame);
if(!framebuffer)
{
return Framework::CBitmap();
}
else
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
2021-01-10 12:15:05 +00:00
auto imgbuffer = Framework::CBitmap(framebuffer->m_width * m_fbScale, framebuffer->m_height * m_fbScale, 32);
glReadPixels(0, 0, framebuffer->m_width * m_fbScale, framebuffer->m_height * m_fbScale, GL_BGRA, GL_UNSIGNED_BYTE, imgbuffer.GetPixels());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return imgbuffer;
}
#else
throw std::runtime_error("Feature is not implemented in current backend.");
#endif
}
Framework::CBitmap CGSH_OpenGL::GetDepthbuffer(uint64 frameReg, uint64 zbufReg)
{
return Framework::CBitmap();
}
Framework::CBitmap CGSH_OpenGL::GetTexture(uint64 tex0Reg, uint32 maxMip, uint64 miptbp1Reg, uint64 miptbp2Reg, uint32 mipLevel)
{
Framework::CBitmap result;
2020-01-26 12:40:17 +00:00
SendGSCall([&]() { result = GetTextureImpl(tex0Reg, maxMip, miptbp1Reg, miptbp2Reg, mipLevel); }, true);
return result;
}
Framework::CBitmap CGSH_OpenGL::GetTextureImpl(uint64 tex0Reg, uint32 maxMip, uint64 miptbp1Reg, uint64 miptbp2Reg, uint32 mipLevel)
{
#ifndef GLES_COMPATIBILITY
2023-11-22 13:59:47 -05:00
if(mipLevel != 0)
{
//Not supported
return Framework::CBitmap();
}
auto tex0 = make_convertible<TEX0>(tex0Reg);
auto width = std::max<uint32>(tex0.GetWidth() >> mipLevel, 1);
auto height = std::max<uint32>(tex0.GetHeight() >> mipLevel, 1);
FRAME frame = {};
frame.nPtr = tex0.GetBufPtr() / 8192;
frame.nWidth = tex0.GetWidth() / 64;
frame.nPsm = tex0.nPsm;
auto framebuffer = FindFramebuffer(frame);
if(framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
auto imgbuffer = Framework::CBitmap(width, height, 32);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, imgbuffer.GetPixels());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return imgbuffer;
}
auto texInfo = PrepareTexture(tex0);
if(texInfo.textureHandle == 0)
{
return Framework::CBitmap();
}
auto texFormat = GetTextureFormatInfo(tex0.nPsm);
2020-01-26 12:12:50 +00:00
auto bitsPerPixel =
[format = tex0.nPsm]() {
switch(format)
{
case PSMCT16:
return 16;
case PSMT4:
case PSMT8:
case PSMT8H:
case PSMT4HL:
case PSMT4HH:
return 8;
default:
assert(false);
case PSMCT32:
case PSMCT24:
return 32;
}
}();
auto imgbuffer = Framework::CBitmap(width, height, bitsPerPixel);
2020-01-26 12:12:50 +00:00
glGetTexImage(GL_TEXTURE_2D, 0, texFormat.format, texFormat.type, imgbuffer.GetPixels());
glBindTexture(GL_TEXTURE_2D, 0);
return imgbuffer;
#else
throw std::runtime_error("Feature is not implemented in current backend.");
#endif
}
2021-08-20 11:11:45 -04:00
int CGSH_OpenGL::GetFramebufferScale()
{
return m_fbScale;
}
2021-08-19 21:01:17 -04:00
const CGSHandler::VERTEX* CGSH_OpenGL::GetInputVertices() const
{
return m_VtxBuffer;
}
bool CGSH_OpenGL::GetDepthTestingEnabled() const
{
return m_depthTestingEnabled;
}
void CGSH_OpenGL::SetDepthTestingEnabled(bool depthTestingEnabled)
{
m_depthTestingEnabled = depthTestingEnabled;
m_renderState.isValid = false;
}
bool CGSH_OpenGL::GetAlphaBlendingEnabled() const
{
return m_alphaBlendingEnabled;
}
void CGSH_OpenGL::SetAlphaBlendingEnabled(bool alphaBlendingEnabled)
{
m_alphaBlendingEnabled = alphaBlendingEnabled;
m_renderState.isValid = false;
}
bool CGSH_OpenGL::GetAlphaTestingEnabled() const
{
return m_alphaTestingEnabled;
}
void CGSH_OpenGL::SetAlphaTestingEnabled(bool alphaTestingEnabled)
{
m_alphaTestingEnabled = alphaTestingEnabled;
m_renderState.isValid = false;
}
/////////////////////////////////////////////////////////////
// Framebuffer
/////////////////////////////////////////////////////////////
2016-02-26 19:12:50 -05:00
CGSH_OpenGL::CFramebuffer::CFramebuffer(uint32 basePtr, uint32 width, uint32 height, uint32 psm, uint32 scale, bool multisampled)
2018-04-30 21:01:23 +01:00
: m_basePtr(basePtr)
, m_width(width)
, m_height(height)
, m_psm(psm)
{
m_cachedArea.SetArea(psm, basePtr, width, height);
//Build color attachment
glGenTextures(1, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
2016-02-26 19:12:50 -05:00
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, m_width * scale, m_height * scale);
CHECKGLERROR();
2016-02-26 19:12:50 -05:00
if(multisampled)
{
//We also need an attachment for multisampled color
glGenRenderbuffers(1, &m_colorBufferMs);
glBindRenderbuffer(GL_RENDERBUFFER, m_colorBufferMs);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, NUM_SAMPLES, GL_RGBA8, m_width * scale, m_height * scale);
CHECKGLERROR();
}
2018-04-30 21:01:23 +01:00
//Build framebuffer
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
2016-02-26 19:12:50 -05:00
if(multisampled)
{
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorBufferMs);
}
else
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
}
CHECKGLERROR();
2016-02-26 19:12:50 -05:00
if(multisampled)
{
glGenFramebuffers(1, &m_resolveFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_resolveFramebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
CHECKGLERROR();
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
2016-02-26 19:12:50 -05:00
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
CGSH_OpenGL::CFramebuffer::~CFramebuffer()
{
if(m_framebuffer != 0)
{
glDeleteFramebuffers(1, &m_framebuffer);
}
2016-02-26 19:12:50 -05:00
if(m_resolveFramebuffer != 0)
{
glDeleteFramebuffers(1, &m_resolveFramebuffer);
}
if(m_texture != 0)
{
glDeleteTextures(1, &m_texture);
}
2016-02-26 19:12:50 -05:00
if(m_colorBufferMs != 0)
{
glDeleteRenderbuffers(1, &m_colorBufferMs);
}
}
void CGSH_OpenGL::PopulateFramebuffer(const FramebufferPtr& framebuffer)
{
2017-01-16 23:56:36 -05:00
auto texFormat = GetTextureFormatInfo(framebuffer->m_psm);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_copyToFbTexture);
2017-01-16 23:56:36 -05:00
glTexImage2D(GL_TEXTURE_2D, 0, texFormat.internalFormat, framebuffer->m_width, framebuffer->m_height,
2018-04-30 21:01:23 +01:00
0, texFormat.format, texFormat.type, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2018-04-30 21:01:23 +01:00
((this)->*(m_textureUpdater[framebuffer->m_psm]))(framebuffer->m_basePtr,
framebuffer->m_width / 64, 0, 0, framebuffer->m_width, framebuffer->m_height);
CHECKGLERROR();
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
CopyToFb(
2018-04-30 21:01:23 +01:00
0, 0, framebuffer->m_width, framebuffer->m_height,
framebuffer->m_width, framebuffer->m_height,
0, 0, framebuffer->m_width * m_fbScale, framebuffer->m_height * m_fbScale);
framebuffer->m_resolveNeeded = true;
2016-04-16 22:16:49 -04:00
CHECKGLERROR();
}
void CGSH_OpenGL::CommitFramebufferDirtyPages(const FramebufferPtr& framebuffer, unsigned int minY, unsigned int maxY)
{
class CCopyToFbEnabler
{
public:
2016-04-16 23:19:42 -04:00
void EnableCopyToFb(const FramebufferPtr& framebuffer, GLuint copyToFbTexture)
{
if(m_copyToFbEnabled) return;
glDisable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, copyToFbTexture);
2018-04-30 21:01:23 +01:00
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebuffer->m_width, framebuffer->m_height,
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_framebuffer);
m_copyToFbEnabled = true;
}
private:
bool m_copyToFbEnabled = false;
};
auto& cachedArea = framebuffer->m_cachedArea;
auto texturePageSize = CGsPixelFormats::GetPsmPageSize(framebuffer->m_psm);
CCopyToFbEnabler copyToFbEnabler;
while(cachedArea.HasDirtyPages())
{
auto dirtyRect = cachedArea.GetDirtyPageRect();
assert((dirtyRect.width != 0) && (dirtyRect.height != 0));
cachedArea.ClearDirtyPages(dirtyRect);
uint32 texX = dirtyRect.x * texturePageSize.first;
uint32 texY = dirtyRect.y * texturePageSize.second;
uint32 texWidth = dirtyRect.width * texturePageSize.first;
uint32 texHeight = dirtyRect.height * texturePageSize.second;
if(texY >= maxY)
{
//Don't bother
continue;
}
assert(texX < framebuffer->m_width);
assert(texY < framebuffer->m_height);
//assert(texY >= minY);
//assert(texY < maxY);
if((texX + texWidth) > framebuffer->m_width)
{
texWidth = framebuffer->m_width - texX;
}
if((texY + texHeight) > framebuffer->m_height)
{
texHeight = framebuffer->m_height - texY;
}
m_validGlState &= ~(GLSTATE_SCISSOR | GLSTATE_FRAMEBUFFER | GLSTATE_TEXTURE);
copyToFbEnabler.EnableCopyToFb(framebuffer, m_copyToFbTexture);
((this)->*(m_textureUpdater[framebuffer->m_psm]))(framebuffer->m_basePtr, framebuffer->m_width / 64,
2018-04-30 21:01:23 +01:00
texX, texY, texWidth, texHeight);
CopyToFb(
2018-04-30 21:01:23 +01:00
texX, texY, (texX + texWidth), (texY + texHeight),
framebuffer->m_width, framebuffer->m_height,
texX * m_fbScale, texY * m_fbScale, (texX + texWidth) * m_fbScale, (texY + texHeight) * m_fbScale);
framebuffer->m_resolveNeeded = true;
CHECKGLERROR();
}
//Mark all pages as clean, but might be wrong due to range not
//covering an area that might be used later on
cachedArea.ClearDirtyPages();
}
2016-02-26 19:12:50 -05:00
void CGSH_OpenGL::ResolveFramebufferMultisample(const FramebufferPtr& framebuffer, uint32 scale)
{
if(!framebuffer->m_resolveNeeded) return;
m_validGlState &= ~(GLSTATE_SCISSOR | GLSTATE_FRAMEBUFFER);
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->m_resolveFramebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer->m_framebuffer);
glBlitFramebuffer(
2018-04-30 21:01:23 +01:00
0, 0, framebuffer->m_width * scale, framebuffer->m_height * scale,
0, 0, framebuffer->m_width * scale, framebuffer->m_height * scale,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
2016-02-26 19:12:50 -05:00
CHECKGLERROR();
framebuffer->m_resolveNeeded = false;
}
/////////////////////////////////////////////////////////////
// Depthbuffer
/////////////////////////////////////////////////////////////
2016-02-26 19:12:50 -05:00
CGSH_OpenGL::CDepthbuffer::CDepthbuffer(uint32 basePtr, uint32 width, uint32 height, uint32 psm, uint32 scale, bool multisampled)
2018-04-30 21:01:23 +01:00
: m_basePtr(basePtr)
, m_width(width)
, m_height(height)
, m_psm(psm)
, m_depthBuffer(0)
{
//Build depth attachment
glGenRenderbuffers(1, &m_depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_depthBuffer);
2016-02-26 19:12:50 -05:00
if(multisampled)
{
glRenderbufferStorageMultisample(GL_RENDERBUFFER, NUM_SAMPLES, GL_DEPTH_COMPONENT32F, m_width * scale, m_height * scale);
2016-02-26 19:12:50 -05:00
}
else
{
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, m_width * scale, m_height * scale);
2016-02-26 19:12:50 -05:00
}
CHECKGLERROR();
}
CGSH_OpenGL::CDepthbuffer::~CDepthbuffer()
{
if(m_depthBuffer != 0)
{
glDeleteRenderbuffers(1, &m_depthBuffer);
}
}