diff --git a/desmume/src/OGLRender_3_2.h b/desmume/src/OGLRender_3_2.h index 4c3384a82..700b9497c 100644 --- a/desmume/src/OGLRender_3_2.h +++ b/desmume/src/OGLRender_3_2.h @@ -36,6 +36,25 @@ extern const char *FogFragShader_150; extern const char *FramebufferOutputVtxShader_150; extern const char *FramebufferOutput6665FragShader_150; +// A port that wants to use the OpenGL 3.2 renderer must assign the two following functions +// to OGLLoadEntryPoints_3_2_Func and OGLCreateRenderer_3_2_Func, respectively. +// +// In addition, the port must add the following GPU3DInterface objects to core3DList: +// - gpu3Dgl: Automatically selects the most fully featured version of standard OpenGL that +// is available on the host system, prefering OpenGL 3.2 Core Profile. +// - gpu3Dgl_3_2: Selects the OpenGL 3.2 Core Profile renderer, and returns an error if it +// is not available on the host system. +// +// Finally, the port must call GPU->Set3DRendererByID() and pass in the index where +// gpu3Dgl_3_2 exists in core3DList so that the emulator can create the appropriate +// OpenGLRenderer object. +// +// Example code: +// OGLLoadEntryPoints_3_2_Func = &OGLLoadEntryPoints_3_2; +// OGLCreateRenderer_3_2_Func = &OGLCreateRenderer_3_2; +// GPU3DInterface *core3DList[] = { &gpu3DNull, &gpu3DRasterize, &gpu3Dgl_3_2, NULL }; +// GPU->Set3DRendererByID(2); + void OGLLoadEntryPoints_3_2(); void OGLCreateRenderer_3_2(OpenGLRenderer **rendererPtr); diff --git a/desmume/src/OGLRender_ES3.cpp b/desmume/src/OGLRender_ES3.cpp new file mode 100644 index 000000000..374518238 --- /dev/null +++ b/desmume/src/OGLRender_ES3.cpp @@ -0,0 +1,889 @@ +/* + Copyright (C) 2024 DeSmuME team + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the this software. If not, see . +*/ + +#include "OGLRender_ES3.h" + +#include +#include +#include +#include +#include +#include + +#include "utils/bits.h" +#include "common.h" +#include "debug.h" +#include "NDSSystem.h" + + +// Vertex shader for geometry, GLSL ES 3.00 +static const char *GeometryVtxShader_ES300 = {"\ +IN_VTX_POSITION vec4 inPosition;\n\ +IN_VTX_TEXCOORD0 vec2 inTexCoord0;\n\ +IN_VTX_COLOR vec3 inColor; \n\ +\n\ +#if IS_USING_UBO_POLY_STATES\n\ +layout (std140) uniform PolyStates\n\ +{\n\ + ivec4 value[4096];\n\ +} polyState;\n\ +#elif IS_USING_TBO_POLY_STATES\n\ +uniform highp isamplerBuffer PolyStates;\n\ +#else\n\ +uniform highp isampler2D PolyStates;\n\ +#endif\n\ +uniform mediump int polyIndex;\n\ +uniform bool polyDrawShadow;\n\ +\n\ +out vec2 vtxTexCoord;\n\ +out vec4 vtxColor;\n\ +flat out lowp int polyID;\n\ +flat out lowp int polyMode;\n\ +flat out lowp int polyIsWireframe;\n\ +flat out lowp int polyEnableFog;\n\ +flat out lowp int polySetNewDepthForTranslucent;\n\ +flat out lowp int polyEnableTexture;\n\ +flat out lowp int texSingleBitAlpha;\n\ +flat out lowp int polyIsBackFacing;\n\ +flat out lowp int isPolyDrawable;\n\ +\n\ +void main()\n\ +{\n\ +#if IS_USING_UBO_POLY_STATES\n\ + ivec4 polyStateVec = polyState.value[polyIndex >> 2];\n\ + int polyStateBits = polyStateVec[polyIndex & 0x03];\n\ +#elif IS_USING_TBO_POLY_STATES\n\ + int polyStateBits = texelFetch(PolyStates, polyIndex).r;\n\ +#else\n\ + int polyStateBits = texelFetch(PolyStates, ivec2(polyIndex & 0x00FF, (polyIndex >> 8) & 0x007F), 0).r;\n\ +#endif\n\ + int texSizeShiftS = (polyStateBits >> 18) & 0x07;\n\ + int texSizeShiftT = (polyStateBits >> 21) & 0x07;\n\ + \n\ + float polyAlpha = float((polyStateBits >> 8) & 0x1F) / 31.0;\n\ + vec2 polyTexScale = vec2(1.0 / float(8 << texSizeShiftS), 1.0 / float(8 << texSizeShiftT));\n\ + \n\ + polyID = (polyStateBits >> 0) & 0x3F;\n\ + polyMode = (polyStateBits >> 6) & 0x03;\n\ + polyIsWireframe = (polyStateBits >> 13) & 0x01;\n\ + polyEnableFog = (polyStateBits >> 14) & 0x01;\n\ + polySetNewDepthForTranslucent = (polyStateBits >> 15) & 0x01;\n\ + polyEnableTexture = (polyStateBits >> 16) & 0x01;\n\ + texSingleBitAlpha = (polyStateBits >> 17) & 0x01;\n\ + polyIsBackFacing = (polyStateBits >> 24) & 0x01;\n\ + \n\ + isPolyDrawable = int((polyMode != 3) || polyDrawShadow);\n\ + \n\ + mat2 texScaleMtx = mat2( vec2(polyTexScale.x, 0.0), \n\ + vec2( 0.0, polyTexScale.y)); \n\ + \n\ + vtxTexCoord = (texScaleMtx * inTexCoord0) / 16.0;\n\ + vtxColor = vec4(inColor / 63.0, polyAlpha);\n\ + gl_Position = inPosition / 4096.0;\n\ +}\n\ +"}; + +// Fragment shader for geometry, GLSL ES 3.00 +static const char *GeometryFragShader_ES300 = {"\ +in vec2 vtxTexCoord;\n\ +in vec4 vtxColor;\n\ +flat in lowp int polyID;\n\ +flat in lowp int polyMode;\n\ +flat in lowp int polyIsWireframe;\n\ +flat in lowp int polyEnableFog;\n\ +flat in lowp int polySetNewDepthForTranslucent;\n\ +flat in lowp int polyEnableTexture;\n\ +flat in lowp int texSingleBitAlpha;\n\ +flat in lowp int polyIsBackFacing;\n\ +flat in lowp int isPolyDrawable;\n\ +\n\ +layout (std140) uniform RenderStates\n\ +{\n\ + bool enableAntialiasing;\n\ + bool enableFogAlphaOnly;\n\ + int clearPolyID;\n\ + float clearDepth;\n\ + float alphaTestRef;\n\ + float fogOffset;\n\ + float fogStep;\n\ + float pad_0;\n\ + vec4 fogColor;\n\ + vec4 edgeColor[8];\n\ + vec4 toonColor[32];\n\ +} state;\n\ +\n\ +uniform sampler2D texRenderObject;\n\ +uniform bool texDrawOpaque;\n\ +uniform bool drawModeDepthEqualsTest;\n\ +uniform bool polyDrawShadow;\n\ +uniform float polyDepthOffset;\n\ +\n\ +OUT_COLOR vec4 outFragColor;\n\ +\n\ +#if DRAW_MODE_OPAQUE\n\ +OUT_WORKING_BUFFER vec4 outDstBackFacing;\n\ +#elif USE_DEPTH_LEQUAL_POLYGON_FACING\n\ +uniform sampler2D inDstBackFacing;\n\ +#endif\n\ +\n\ +#if ENABLE_EDGE_MARK\n\ +OUT_POLY_ID vec4 outPolyID;\n\ +#endif\n\ +#if ENABLE_FOG\n\ +OUT_FOG_ATTRIBUTES vec4 outFogAttributes;\n\ +#endif\n\ +\n\ +void main()\n\ +{\n\ +#if USE_DEPTH_LEQUAL_POLYGON_FACING && !DRAW_MODE_OPAQUE\n\ + bool isOpaqueDstBackFacing = bool( texelFetch(inDstBackFacing, ivec2(gl_FragCoord.xy), 0).r );\n\ + if ( drawModeDepthEqualsTest && (bool(polyIsBackFacing) || !isOpaqueDstBackFacing) )\n\ + {\n\ + discard;\n\ + }\n\ +#endif\n\ + \n\ + vec4 mainTexColor = (ENABLE_TEXTURE_SAMPLING && bool(polyEnableTexture)) ? texture(texRenderObject, vtxTexCoord) : vec4(1.0, 1.0, 1.0, 1.0);\n\ + \n\ + if (!bool(texSingleBitAlpha))\n\ + {\n\ + if (texDrawOpaque)\n\ + {\n\ + if ( (polyMode != 1) && (mainTexColor.a <= 0.999) )\n\ + {\n\ + discard;\n\ + }\n\ + }\n\ + else\n\ + {\n\ + if ( ((polyMode != 1) && (mainTexColor.a * vtxColor.a > 0.999)) || ((polyMode == 1) && (vtxColor.a > 0.999)) )\n\ + {\n\ + discard;\n\ + }\n\ + }\n\ + }\n\ +#if USE_TEXTURE_SMOOTHING\n\ + else\n\ + {\n\ + if (mainTexColor.a < 0.500)\n\ + {\n\ + mainTexColor.a = 0.0;\n\ + }\n\ + else\n\ + {\n\ + mainTexColor.rgb = mainTexColor.rgb / mainTexColor.a;\n\ + mainTexColor.a = 1.0;\n\ + }\n\ + }\n\ +#endif\n\ + \n\ + outFragColor = mainTexColor * vtxColor;\n\ + \n\ + if (polyMode == 1)\n\ + {\n\ + outFragColor.rgb = (ENABLE_TEXTURE_SAMPLING && bool(polyEnableTexture)) ? mix(vtxColor.rgb, mainTexColor.rgb, mainTexColor.a) : vtxColor.rgb;\n\ + outFragColor.a = vtxColor.a;\n\ + }\n\ + else if (polyMode == 2)\n\ + {\n\ + vec3 newToonColor = state.toonColor[int((vtxColor.r * 31.0) + 0.5)].rgb;\n\ +#if TOON_SHADING_MODE\n\ + outFragColor.rgb = min((mainTexColor.rgb * vtxColor.r) + newToonColor.rgb, 1.0);\n\ +#else\n\ + outFragColor.rgb = mainTexColor.rgb * newToonColor.rgb;\n\ +#endif\n\ + }\n\ + else if ((polyMode == 3) && polyDrawShadow)\n\ + {\n\ + outFragColor = vtxColor;\n\ + }\n\ + \n\ + if ( (isPolyDrawable != 0) && ((outFragColor.a < 0.001) || (ENABLE_ALPHA_TEST && outFragColor.a < state.alphaTestRef)) )\n\ + {\n\ + discard;\n\ + }\n\ +#if ENABLE_EDGE_MARK\n\ + outPolyID = (isPolyDrawable != 0) ? vec4( float(polyID)/63.0, float(polyIsWireframe == 1), 0.0, float(outFragColor.a > 0.999) ) : vec4(0.0, 0.0, 0.0, 0.0);\n\ +#endif\n\ +#if ENABLE_FOG\n\ + outFogAttributes = (isPolyDrawable != 0) ? vec4( float(polyEnableFog), 0.0, 0.0, float((outFragColor.a > 0.999) ? 1.0 : 0.5) ) : vec4(0.0, 0.0, 0.0, 0.0);\n\ +#endif\n\ +#if DRAW_MODE_OPAQUE\n\ + outDstBackFacing = vec4(float(polyIsBackFacing), 0.0, 0.0, 1.0);\n\ +#endif\n\ + \n\ +#if USE_NDS_DEPTH_CALCULATION || ENABLE_FOG\n\ + // It is tempting to perform the NDS depth calculation in the vertex shader rather than in the fragment shader.\n\ + // Resist this temptation! It is much more reliable to do the depth calculation in the fragment shader due to\n\ + // subtle interpolation differences between various GPUs and/or drivers. If the depth calculation is not done\n\ + // here, then it is very possible for the user to experience Z-fighting in certain rendering situations.\n\ + \n\ + #if ENABLE_W_DEPTH\n\ + gl_FragDepth = clamp( ((1.0/gl_FragCoord.w) * (4096.0/16777215.0)) + polyDepthOffset, 0.0, 1.0 );\n\ + #else\n\ + // hack: when using z-depth, drop some LSBs so that the overworld map in Dragon Quest IV shows up correctly\n\ + gl_FragDepth = clamp( (floor(gl_FragCoord.z * 4194303.0) * (4.0/16777215.0)) + polyDepthOffset, 0.0, 1.0 );\n\ + #endif\n\ +#endif\n\ +}\n\ +"}; + +void OGLLoadEntryPoints_ES_3_0() +{ + OGLLoadEntryPoints_3_2(); +} + +void OGLCreateRenderer_ES_3_0(OpenGLRenderer **rendererPtr) +{ + if (IsOpenGLDriverVersionSupported(3, 0, 0)) + { + *rendererPtr = new OpenGLESRenderer_3_0; + (*rendererPtr)->SetVersion(3, 0, 0); + } +} + +OpenGLESRenderer_3_0::OpenGLESRenderer_3_0() +{ + _variantID = OpenGLVariantID_ES_3_0; +} + +Render3DError OpenGLESRenderer_3_0::InitExtensions() +{ + OGLRenderRef &OGLRef = *this->ref; + Render3DError error = OGLERROR_NOERR; + + // Get OpenGL extensions + std::set oglExtensionSet; + this->GetExtensionSet(&oglExtensionSet); + + // Get host GPU device properties + GLint maxUBOSize = 0; + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUBOSize); + this->_is64kUBOSupported = (maxUBOSize >= 65536); + + // TBOs are only supported in ES 3.2. + this->_isTBOSupported = IsOpenGLDriverVersionSupported(3, 2, 0); + + GLfloat maxAnisotropyOGL = 1.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyOGL); + this->_deviceInfo.maxAnisotropy = (float)maxAnisotropyOGL; + + // OpenGL ES 3.0 needs to look up the best format and data type for glReadPixels. + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &OGLRef.readPixelsBestFormat); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &OGLRef.readPixelsBestDataType); + + this->_deviceInfo.isEdgeMarkSupported = true; + this->_deviceInfo.isFogSupported = true; + + // Need to generate this texture first because FBO creation needs it. + // This texture is only required by shaders, and so if shader creation + // fails, then we can immediately delete this texture if an error occurs. + glGenTextures(1, &OGLRef.texFinalColorID); + glActiveTexture(GL_TEXTURE0 + OGLTextureUnitID_FinalColor); + glBindTexture(GL_TEXTURE_2D, OGLRef.texFinalColorID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)this->_framebufferWidth, (GLsizei)this->_framebufferHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glActiveTexture(GL_TEXTURE0); + + // OpenGL ES v3.0 should have all the necessary features to be able to flip and convert the framebuffer. + this->willFlipOnlyFramebufferOnGPU = true; + this->willFlipAndConvertFramebufferOnGPU = true; + + this->_enableTextureSmoothing = CommonSettings.GFX3D_Renderer_TextureSmoothing; + this->_emulateShadowPolygon = CommonSettings.OpenGL_Emulation_ShadowPolygon; + this->_emulateSpecialZeroAlphaBlending = CommonSettings.OpenGL_Emulation_SpecialZeroAlphaBlending; + this->_emulateNDSDepthCalculation = CommonSettings.OpenGL_Emulation_NDSDepthCalculation; + this->_emulateDepthLEqualPolygonFacing = CommonSettings.OpenGL_Emulation_DepthLEqualPolygonFacing; + + // Load and create shaders. Return on any error, since ES 3.0 makes shaders mandatory. + this->isShaderSupported = true; + + error = this->CreateGeometryPrograms(); + if (error != OGLERROR_NOERR) + { + glUseProgram(0); + this->DestroyGeometryPrograms(); + this->isShaderSupported = false; + + return error; + } + + error = this->CreateGeometryZeroDstAlphaProgram(GeometryZeroDstAlphaPixelMaskVtxShader_150, GeometryZeroDstAlphaPixelMaskFragShader_150); + if (error != OGLERROR_NOERR) + { + glUseProgram(0); + this->DestroyGeometryPrograms(); + this->isShaderSupported = false; + + return error; + } + + INFO("OpenGL: Successfully created geometry shaders.\n"); + error = this->InitPostprocessingPrograms(EdgeMarkVtxShader_150, + EdgeMarkFragShader_150, + FramebufferOutputVtxShader_150, + FramebufferOutput6665FragShader_150, + NULL); + if (error != OGLERROR_NOERR) + { + glUseProgram(0); + this->DestroyGeometryPrograms(); + this->DestroyGeometryZeroDstAlphaProgram(); + this->isShaderSupported = false; + + return error; + } + + this->isVBOSupported = true; + this->CreateVBOs(); + + this->isPBOSupported = true; + this->CreatePBOs(); + + this->isVAOSupported = true; + this->CreateVAOs(); + + // Load and create FBOs. Return on any error, since v3.2 Core Profile makes FBOs mandatory. + this->isFBOSupported = true; + error = this->CreateFBOs(); + if (error != OGLERROR_NOERR) + { + this->isFBOSupported = false; + return error; + } + + this->isMultisampledFBOSupported = true; + this->_selectedMultisampleSize = CommonSettings.GFX3D_Renderer_MultisampleSize; + + GLint maxSamplesOGL = 0; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamplesOGL); + this->_deviceInfo.maxSamples = (u8)maxSamplesOGL; + + if (this->_deviceInfo.maxSamples >= 2) + { + // Try and initialize the multisampled FBOs with the GFX3D_Renderer_MultisampleSize. + // However, if the client has this set to 0, then set sampleSize to 2 in order to + // force the generation and the attachments of the buffers at a meaningful sample + // size. If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffer + // memory afterwards. + GLsizei sampleSize = this->GetLimitedMultisampleSize(); + if (sampleSize == 0) + { + sampleSize = 2; + } + + error = this->CreateMultisampledFBO(sampleSize); + if (error != OGLERROR_NOERR) + { + this->isMultisampledFBOSupported = false; + } + + // If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffers now + // in order to save some memory. + if (this->_selectedMultisampleSize == 0) + { + this->ResizeMultisampledFBOs(0); + } + } + else + { + this->isMultisampledFBOSupported = false; + INFO("OpenGL: Driver does not support at least 2x multisampled FBOs.\n"); + } + + this->_isDepthLEqualPolygonFacingSupported = true; + this->_enableMultisampledRendering = ((this->_selectedMultisampleSize >= 2) && this->isMultisampledFBOSupported); + + this->InitFinalRenderStates(&oglExtensionSet); // This must be done last + + return OGLERROR_NOERR; +} + +Render3DError OpenGLESRenderer_3_0::CreateGeometryPrograms() +{ + Render3DError error = OGLERROR_NOERR; + OGLRenderRef &OGLRef = *this->ref; + + // Create shader resources. + if (OGLRef.uboRenderStatesID == 0) + { + glGenBuffers(1, &OGLRef.uboRenderStatesID); + glBindBuffer(GL_UNIFORM_BUFFER, OGLRef.uboRenderStatesID); + glBufferData(GL_UNIFORM_BUFFER, sizeof(OGLRenderStates), NULL, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, OGLBindingPointID_RenderStates, OGLRef.uboRenderStatesID); + } + + if (this->_is64kUBOSupported) + { + // Try transferring the polygon states through a UBO first. This is the fastest method, + // but requires a GPU that supports 64k UBO transfers. + if (OGLRef.uboPolyStatesID == 0) + { + glGenBuffers(1, &OGLRef.uboPolyStatesID); + glBindBuffer(GL_UNIFORM_BUFFER, OGLRef.uboPolyStatesID); + glBufferData(GL_UNIFORM_BUFFER, MAX_CLIPPED_POLY_COUNT_FOR_UBO * sizeof(OGLPolyStates), NULL, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, OGLBindingPointID_PolyStates, OGLRef.uboPolyStatesID); + } + } +#ifdef GL_ES_VERSION_3_2 + else if (this->_isTBOSupported) + { + // If for some reason the GPU doesn't support 64k UBOs but still supports OpenGL ES 3.2, + // then use a TBO as the second fastest method. + if (OGLRef.tboPolyStatesID == 0) + { + // Set up poly states TBO + glGenBuffers(1, &OGLRef.tboPolyStatesID); + glBindBuffer(GL_TEXTURE_BUFFER, OGLRef.tboPolyStatesID); + glBufferData(GL_TEXTURE_BUFFER, CLIPPED_POLYLIST_SIZE * sizeof(OGLPolyStates), NULL, GL_DYNAMIC_DRAW); + + glGenTextures(1, &OGLRef.texPolyStatesID); + glActiveTexture(GL_TEXTURE0 + OGLTextureUnitID_PolyStates); + glBindTexture(GL_TEXTURE_BUFFER, OGLRef.texPolyStatesID); + glTexBuffer(GL_TEXTURE_BUFFER, GL_R32I, OGLRef.tboPolyStatesID); + glActiveTexture(GL_TEXTURE0); + } + } +#endif + else + { + // For compatibility reasons, we can transfer the polygon states through a plain old + // integer texture. + glGenTextures(1, &OGLRef.texPolyStatesID); + glActiveTexture(GL_TEXTURE0 + OGLTextureUnitID_PolyStates); + glBindTexture(GL_TEXTURE_2D, OGLRef.texPolyStatesID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, 256, 128, 0, GL_RED_INTEGER, GL_INT, NULL); + glActiveTexture(GL_TEXTURE0); + } + + glGenTextures(1, &OGLRef.texFogDensityTableID); + glActiveTexture(GL_TEXTURE0 + OGLTextureUnitID_LookupTable); + glBindTexture(GL_TEXTURE_2D, OGLRef.texFogDensityTableID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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_R8, 32, 1, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); + glActiveTexture(GL_TEXTURE0); + + OGLGeometryFlags programFlags; + programFlags.value = 0; + + std::stringstream shaderHeader; + shaderHeader << "#version 300 es\n"; + shaderHeader << "precision highp float;\n"; + shaderHeader << "precision highp int;\n"; + shaderHeader << "\n"; + + std::stringstream vsHeader; + vsHeader << "#define IN_VTX_POSITION layout (location = " << OGLVertexAttributeID_Position << ") in\n"; + vsHeader << "#define IN_VTX_TEXCOORD0 layout (location = " << OGLVertexAttributeID_TexCoord0 << ") in\n"; + vsHeader << "#define IN_VTX_COLOR layout (location = " << OGLVertexAttributeID_Color << ") in\n"; + vsHeader << "\n"; + vsHeader << "#define IS_USING_UBO_POLY_STATES " << ((OGLRef.uboPolyStatesID != 0) ? 1 : 0) << "\n"; + vsHeader << "#define IS_USING_TBO_POLY_STATES " << ((OGLRef.tboPolyStatesID != 0) ? 1 : 0) << "\n"; + vsHeader << "#define DEPTH_EQUALS_TEST_TOLERANCE " << DEPTH_EQUALS_TEST_TOLERANCE << ".0\n"; + vsHeader << "\n"; + + std::string vtxShaderCode = shaderHeader.str() + vsHeader.str() + std::string(GeometryVtxShader_ES300); + + for (size_t flagsValue = 0; flagsValue < 128; flagsValue++, programFlags.value++) + { + std::stringstream shaderFlags; + if (this->_isShaderFixedLocationSupported) + { + shaderFlags << "#define OUT_COLOR layout (location = 0) out\n"; + shaderFlags << "#define OUT_WORKING_BUFFER layout (location = " << GeometryAttachmentWorkingBuffer[programFlags.DrawBuffersMode] << ") out\n"; + shaderFlags << "#define OUT_POLY_ID layout (location = " << GeometryAttachmentPolyID[programFlags.DrawBuffersMode] << ") out\n"; + shaderFlags << "#define OUT_FOG_ATTRIBUTES layout (location = " << GeometryAttachmentFogAttributes[programFlags.DrawBuffersMode] << ") out\n"; + } + else + { + shaderFlags << "#define OUT_COLOR out\n"; + shaderFlags << "#define OUT_WORKING_BUFFER out\n"; + shaderFlags << "#define OUT_POLY_ID out\n"; + shaderFlags << "#define OUT_FOG_ATTRIBUTES out\n"; + } + shaderFlags << "\n"; + shaderFlags << "#define USE_TEXTURE_SMOOTHING " << ((this->_enableTextureSmoothing) ? 1 : 0) << "\n"; + shaderFlags << "#define USE_NDS_DEPTH_CALCULATION " << ((this->_emulateNDSDepthCalculation) ? 1 : 0) << "\n"; + shaderFlags << "#define USE_DEPTH_LEQUAL_POLYGON_FACING " << ((this->_emulateDepthLEqualPolygonFacing) ? 1 : 0) << "\n"; + shaderFlags << "\n"; + shaderFlags << "#define ENABLE_W_DEPTH " << ((programFlags.EnableWDepth) ? 1 : 0) << "\n"; + shaderFlags << "#define ENABLE_ALPHA_TEST " << ((programFlags.EnableAlphaTest) ? "true\n" : "false\n"); + shaderFlags << "#define ENABLE_TEXTURE_SAMPLING " << ((programFlags.EnableTextureSampling) ? "true\n" : "false\n"); + shaderFlags << "#define TOON_SHADING_MODE " << ((programFlags.ToonShadingMode) ? 1 : 0) << "\n"; + shaderFlags << "#define ENABLE_FOG " << ((programFlags.EnableFog) ? 1 : 0) << "\n"; + shaderFlags << "#define ENABLE_EDGE_MARK " << ((programFlags.EnableEdgeMark) ? 1 : 0) << "\n"; + shaderFlags << "#define DRAW_MODE_OPAQUE " << ((programFlags.OpaqueDrawMode) ? 1 : 0) << "\n"; + shaderFlags << "\n"; + + std::string fragShaderCode = shaderHeader.str() + shaderFlags.str() + std::string(GeometryFragShader_ES300); + + error = this->ShaderProgramCreate(OGLRef.vertexGeometryShaderID, + OGLRef.fragmentGeometryShaderID[flagsValue], + OGLRef.programGeometryID[flagsValue], + vtxShaderCode.c_str(), + fragShaderCode.c_str()); + if (error != OGLERROR_NOERR) + { + INFO("OpenGL: Failed to create the GEOMETRY shader program.\n"); + glUseProgram(0); + this->DestroyGeometryPrograms(); + return error; + } + + glLinkProgram(OGLRef.programGeometryID[flagsValue]); + if (!this->ValidateShaderProgramLink(OGLRef.programGeometryID[flagsValue])) + { + INFO("OpenGL: Failed to link the GEOMETRY shader program.\n"); + glUseProgram(0); + this->DestroyGeometryPrograms(); + return OGLERROR_SHADER_CREATE_ERROR; + } + + glValidateProgram(OGLRef.programGeometryID[flagsValue]); + glUseProgram(OGLRef.programGeometryID[flagsValue]); + + // Set up render states UBO + const GLuint uniformBlockRenderStates = glGetUniformBlockIndex(OGLRef.programGeometryID[flagsValue], "RenderStates"); + glUniformBlockBinding(OGLRef.programGeometryID[flagsValue], uniformBlockRenderStates, OGLBindingPointID_RenderStates); + + GLint uboSize = 0; + glGetActiveUniformBlockiv(OGLRef.programGeometryID[flagsValue], uniformBlockRenderStates, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize); + assert(uboSize == sizeof(OGLRenderStates)); + + const GLint uniformTexRenderObject = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "texRenderObject"); + glUniform1i(uniformTexRenderObject, 0); + + if (OGLRef.uboPolyStatesID != 0) + { + const GLuint uniformBlockPolyStates = glGetUniformBlockIndex(OGLRef.programGeometryID[flagsValue], "PolyStates"); + glUniformBlockBinding(OGLRef.programGeometryID[flagsValue], uniformBlockPolyStates, OGLBindingPointID_PolyStates); + } + else + { + const GLint uniformTexBufferPolyStates = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "PolyStates"); + glUniform1i(uniformTexBufferPolyStates, OGLTextureUnitID_PolyStates); + } + + if (this->_emulateDepthLEqualPolygonFacing && !programFlags.OpaqueDrawMode) + { + const GLint uniformTexBackfacing = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "inDstBackFacing"); + glUniform1i(uniformTexBackfacing, OGLTextureUnitID_FinalColor); + } + + OGLRef.uniformTexDrawOpaque[flagsValue] = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "texDrawOpaque"); + OGLRef.uniformDrawModeDepthEqualsTest[flagsValue] = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "drawModeDepthEqualsTest"); + OGLRef.uniformPolyDrawShadow[flagsValue] = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "polyDrawShadow"); + OGLRef.uniformPolyStateIndex[flagsValue] = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "polyIndex"); + OGLRef.uniformPolyDepthOffset[flagsValue] = glGetUniformLocation(OGLRef.programGeometryID[flagsValue], "polyDepthOffset"); + } + + return error; +} + +Render3DError OpenGLESRenderer_3_0::CreateGeometryZeroDstAlphaProgram(const char *vtxShaderCString, const char *fragShaderCString) +{ + Render3DError error = OGLERROR_NOERR; + OGLRenderRef &OGLRef = *this->ref; + + if ( (vtxShaderCString == NULL) || (fragShaderCString == NULL) ) + { + return error; + } + + std::stringstream shaderHeader; + shaderHeader << "#version 300 es\n"; + shaderHeader << "precision highp float;\n"; + shaderHeader << "precision highp int;\n"; + shaderHeader << "\n"; + + std::stringstream vsHeader; + vsHeader << "#define IN_VTX_POSITION layout (location = " << OGLVertexAttributeID_Position << ") in\n"; + vsHeader << "#define IN_VTX_TEXCOORD0 layout (location = " << OGLVertexAttributeID_TexCoord0 << ") in\n"; + vsHeader << "#define IN_VTX_COLOR layout (location = " << OGLVertexAttributeID_Color << ") in\n"; + + std::string vtxShaderCode = shaderHeader.str() + vsHeader.str() + std::string(vtxShaderCString); + std::string fragShaderCode = shaderHeader.str() + std::string(fragShaderCString); + + error = this->ShaderProgramCreate(OGLRef.vtxShaderGeometryZeroDstAlphaID, + OGLRef.fragShaderGeometryZeroDstAlphaID, + OGLRef.programGeometryZeroDstAlphaID, + vtxShaderCode.c_str(), + fragShaderCode.c_str()); + if (error != OGLERROR_NOERR) + { + INFO("OpenGL: Failed to create the GEOMETRY ZERO DST ALPHA shader program.\n"); + glUseProgram(0); + this->DestroyGeometryZeroDstAlphaProgram(); + return error; + } + + glLinkProgram(OGLRef.programGeometryZeroDstAlphaID); + if (!this->ValidateShaderProgramLink(OGLRef.programGeometryZeroDstAlphaID)) + { + INFO("OpenGL: Failed to link the GEOMETRY ZERO DST ALPHA shader program.\n"); + glUseProgram(0); + this->DestroyGeometryZeroDstAlphaProgram(); + return OGLERROR_SHADER_CREATE_ERROR; + } + + glValidateProgram(OGLRef.programGeometryZeroDstAlphaID); + glUseProgram(OGLRef.programGeometryZeroDstAlphaID); + + const GLint uniformTexGColor = glGetUniformLocation(OGLRef.programGeometryZeroDstAlphaID, "texInFragColor"); + glUniform1i(uniformTexGColor, OGLTextureUnitID_GColor); + + return OGLERROR_NOERR; +} + +Render3DError OpenGLESRenderer_3_0::CreateEdgeMarkProgram(const char *vtxShaderCString, const char *fragShaderCString) +{ + Render3DError error = OGLERROR_NOERR; + OGLRenderRef &OGLRef = *this->ref; + + if ( (vtxShaderCString == NULL) || (fragShaderCString == NULL) ) + { + return error; + } + + std::stringstream shaderHeader; + shaderHeader << "#version 300 es\n"; + shaderHeader << "precision highp float;\n"; + shaderHeader << "precision highp int;\n"; + shaderHeader << "\n"; + shaderHeader << "#define FRAMEBUFFER_SIZE_X " << this->_framebufferWidth << ".0 \n"; + shaderHeader << "#define FRAMEBUFFER_SIZE_Y " << this->_framebufferHeight << ".0 \n"; + shaderHeader << "\n"; + + std::stringstream vsHeader; + vsHeader << "#define IN_VTX_POSITION layout (location = " << OGLVertexAttributeID_Position << ") in\n"; + vsHeader << "#define IN_VTX_TEXCOORD0 layout (location = " << OGLVertexAttributeID_TexCoord0 << ") in\n"; + vsHeader << "#define IN_VTX_COLOR layout (location = " << OGLVertexAttributeID_Color << ") in\n"; + + std::stringstream fsHeader; + fsHeader << "#define OUT_COLOR layout (location = 0) out\n"; + + std::string vtxShaderCode = shaderHeader.str() + vsHeader.str() + std::string(vtxShaderCString); + std::string fragShaderCode = shaderHeader.str() + fsHeader.str() + std::string(fragShaderCString); + + error = this->ShaderProgramCreate(OGLRef.vertexEdgeMarkShaderID, + OGLRef.fragmentEdgeMarkShaderID, + OGLRef.programEdgeMarkID, + vtxShaderCode.c_str(), + fragShaderCode.c_str()); + if (error != OGLERROR_NOERR) + { + INFO("OpenGL: Failed to create the EDGE MARK shader program.\n"); + glUseProgram(0); + this->DestroyEdgeMarkProgram(); + return error; + } + + glLinkProgram(OGLRef.programEdgeMarkID); + if (!this->ValidateShaderProgramLink(OGLRef.programEdgeMarkID)) + { + INFO("OpenGL: Failed to link the EDGE MARK shader program.\n"); + glUseProgram(0); + this->DestroyEdgeMarkProgram(); + return OGLERROR_SHADER_CREATE_ERROR; + } + + glValidateProgram(OGLRef.programEdgeMarkID); + glUseProgram(OGLRef.programEdgeMarkID); + + const GLuint uniformBlockRenderStates = glGetUniformBlockIndex(OGLRef.programEdgeMarkID, "RenderStates"); + glUniformBlockBinding(OGLRef.programEdgeMarkID, uniformBlockRenderStates, OGLBindingPointID_RenderStates); + + const GLint uniformTexGDepth = glGetUniformLocation(OGLRef.programEdgeMarkID, "texInFragDepth"); + const GLint uniformTexGPolyID = glGetUniformLocation(OGLRef.programEdgeMarkID, "texInPolyID"); + glUniform1i(uniformTexGDepth, OGLTextureUnitID_DepthStencil); + glUniform1i(uniformTexGPolyID, OGLTextureUnitID_GPolyID); + + return OGLERROR_NOERR; +} + +Render3DError OpenGLESRenderer_3_0::CreateFogProgram(const OGLFogProgramKey fogProgramKey, const char *vtxShaderCString, const char *fragShaderCString) +{ + Render3DError error = OGLERROR_NOERR; + OGLRenderRef &OGLRef = *this->ref; + + if (vtxShaderCString == NULL) + { + INFO("OpenGL: The FOG vertex shader is unavailable.\n"); + error = OGLERROR_VERTEX_SHADER_PROGRAM_LOAD_ERROR; + return error; + } + else if (fragShaderCString == NULL) + { + INFO("OpenGL: The FOG fragment shader is unavailable.\n"); + error = OGLERROR_FRAGMENT_SHADER_PROGRAM_LOAD_ERROR; + return error; + } + + const s32 fogOffset = fogProgramKey.offset; + const GLfloat fogOffsetf = (GLfloat)fogOffset / 32767.0f; + const s32 fogStep = 0x0400 >> fogProgramKey.shift; + + std::stringstream shaderHeader; + shaderHeader << "#version 300 es\n"; + shaderHeader << "precision highp float;\n"; + shaderHeader << "precision highp int;\n"; + shaderHeader << "\n"; + + std::stringstream vsHeader; + vsHeader << "#define IN_VTX_POSITION layout (location = " << OGLVertexAttributeID_Position << ") in\n"; + vsHeader << "#define IN_VTX_TEXCOORD0 layout (location = " << OGLVertexAttributeID_TexCoord0 << ") in\n"; + vsHeader << "#define IN_VTX_COLOR layout (location = " << OGLVertexAttributeID_Color << ") in\n"; + + std::stringstream fsHeader; + fsHeader << "#define USE_DUAL_SOURCE_BLENDING " << ((this->_isDualSourceBlendingSupported) ? 1 : 0) << "\n"; + fsHeader << "\n"; + fsHeader << "#define FOG_OFFSET " << fogOffset << "\n"; + fsHeader << "#define FOG_OFFSETF " << fogOffsetf << (((fogOffsetf == 0.0f) || (fogOffsetf == 1.0f)) ? ".0" : "") << "\n"; + fsHeader << "#define FOG_STEP " << fogStep << "\n"; + fsHeader << "\n"; + fsHeader << "#define OUT_FOG_COLOR layout (location = 0, index = 0) out\n"; + fsHeader << "#define OUT_FOG_WEIGHT layout (location = 0, index = 1) out\n"; + fsHeader << "#define OUT_COLOR layout (location = 0) out\n"; + + std::string vtxShaderCode = shaderHeader.str() + vsHeader.str() + std::string(vtxShaderCString); + std::string fragShaderCode = shaderHeader.str() + fsHeader.str() + std::string(fragShaderCString); + + OGLFogShaderID shaderID; + shaderID.program = 0; + shaderID.fragShader = 0; + + error = this->ShaderProgramCreate(OGLRef.vertexFogShaderID, + shaderID.fragShader, + shaderID.program, + vtxShaderCode.c_str(), + fragShaderCode.c_str()); + + this->_fogProgramMap[fogProgramKey.key] = shaderID; + + if (error != OGLERROR_NOERR) + { + INFO("OpenGL: Failed to create the FOG shader program.\n"); + glUseProgram(0); + this->DestroyFogProgram(fogProgramKey); + return error; + } + + glLinkProgram(shaderID.program); + if (!this->ValidateShaderProgramLink(shaderID.program)) + { + INFO("OpenGL: Failed to link the FOG shader program.\n"); + glUseProgram(0); + this->DestroyFogProgram(fogProgramKey); + return OGLERROR_SHADER_CREATE_ERROR; + } + + glValidateProgram(shaderID.program); + glUseProgram(shaderID.program); + + const GLuint uniformBlockRenderStates = glGetUniformBlockIndex(shaderID.program, "RenderStates"); + glUniformBlockBinding(shaderID.program, uniformBlockRenderStates, OGLBindingPointID_RenderStates); + + const GLint uniformTexGDepth = glGetUniformLocation(shaderID.program, "texInFragDepth"); + const GLint uniformTexGFog = glGetUniformLocation(shaderID.program, "texInFogAttributes"); + const GLint uniformTexFogDensityTable = glGetUniformLocation(shaderID.program, "texFogDensityTable"); + glUniform1i(uniformTexGDepth, OGLTextureUnitID_DepthStencil); + glUniform1i(uniformTexGFog, OGLTextureUnitID_FogAttr); + glUniform1i(uniformTexFogDensityTable, OGLTextureUnitID_LookupTable); + + const GLint uniformTexGColor = glGetUniformLocation(shaderID.program, "texInFragColor"); + glUniform1i(uniformTexGColor, OGLTextureUnitID_GColor); + + return OGLERROR_NOERR; +} + +Render3DError OpenGLESRenderer_3_0::CreateFramebufferOutput6665Program(const size_t outColorIndex, const char *vtxShaderCString, const char *fragShaderCString) +{ + Render3DError error = OGLERROR_NOERR; + OGLRenderRef &OGLRef = *this->ref; + + if ( (vtxShaderCString == NULL) || (fragShaderCString == NULL) ) + { + return error; + } + + std::stringstream shaderHeader; + shaderHeader << "#version 300 es\n"; + shaderHeader << "precision highp float;\n"; + shaderHeader << "precision highp int;\n"; + shaderHeader << "\n"; + shaderHeader << "#define FRAMEBUFFER_SIZE_X " << this->_framebufferWidth << ".0 \n"; + shaderHeader << "#define FRAMEBUFFER_SIZE_Y " << this->_framebufferHeight << ".0 \n"; + shaderHeader << "\n"; + + std::stringstream vsHeader; + vsHeader << "#define IN_VTX_POSITION layout (location = " << OGLVertexAttributeID_Position << ") in\n"; + vsHeader << "#define IN_VTX_TEXCOORD0 layout (location = " << OGLVertexAttributeID_TexCoord0 << ") in\n"; + vsHeader << "#define IN_VTX_COLOR layout (location = " << OGLVertexAttributeID_Color << ") in\n"; + + std::stringstream fsHeader; + fsHeader << "#define OUT_COLOR layout (location = 0) out\n"; + + std::string vtxShaderCode = shaderHeader.str() + vsHeader.str() + std::string(vtxShaderCString); + std::string fragShaderCode = shaderHeader.str() + fsHeader.str() + std::string(fragShaderCString); + + error = this->ShaderProgramCreate(OGLRef.vertexFramebufferOutput6665ShaderID, + OGLRef.fragmentFramebufferRGBA6665OutputShaderID, + OGLRef.programFramebufferRGBA6665OutputID[outColorIndex], + vtxShaderCode.c_str(), + fragShaderCode.c_str()); + if (error != OGLERROR_NOERR) + { + INFO("OpenGL: Failed to create the FRAMEBUFFER OUTPUT RGBA6665 shader program.\n"); + glUseProgram(0); + this->DestroyFramebufferOutput6665Programs(); + return error; + } + + glLinkProgram(OGLRef.programFramebufferRGBA6665OutputID[outColorIndex]); + if (!this->ValidateShaderProgramLink(OGLRef.programFramebufferRGBA6665OutputID[outColorIndex])) + { + INFO("OpenGL: Failed to link the FRAMEBUFFER OUTPUT RGBA6665 shader program.\n"); + glUseProgram(0); + this->DestroyFramebufferOutput6665Programs(); + return OGLERROR_SHADER_CREATE_ERROR; + } + + glValidateProgram(OGLRef.programFramebufferRGBA6665OutputID[outColorIndex]); + glUseProgram(OGLRef.programFramebufferRGBA6665OutputID[outColorIndex]); + + const GLint uniformTexGColor = glGetUniformLocation(OGLRef.programFramebufferRGBA6665OutputID[outColorIndex], "texInFragColor"); + if (outColorIndex == 0) + { + glUniform1i(uniformTexGColor, OGLTextureUnitID_FinalColor); + } + else + { + glUniform1i(uniformTexGColor, OGLTextureUnitID_GColor); + } + + return OGLERROR_NOERR; +} diff --git a/desmume/src/OGLRender_ES3.h b/desmume/src/OGLRender_ES3.h new file mode 100644 index 000000000..86c05933a --- /dev/null +++ b/desmume/src/OGLRender_ES3.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2024 DeSmuME team + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the this software. If not, see . +*/ + +#ifndef OGLRENDER_ES3_H +#define OGLRENDER_ES3_H + +#include "OGLRender_3_2.h" + +// A port that wants to use the OpenGL ES renderer must assign the two following functions +// to OGLLoadEntryPoints_ES_3_0_Func and OGLCreateRenderer_ES_3_0_Func, respectively. +// +// In addition, the port must add the following GPU3DInterface objects to core3DList: +// - gpu3Dgl_ES_3_0: Selects the OpenGL ES 3.0 renderer, and returns an error if it is +// not available on the host system. +// +// Finally, the port must call GPU->Set3DRendererByID() and pass in the index where +// gpu3Dgl_ES_3_0 exists in core3DList so that the emulator can create the appropriate +// OpenGLRenderer object. +// +// Example code: +// OGLLoadEntryPoints_ES_3_0_Func = &OGLLoadEntryPoints_ES_3_0; +// OGLCreateRenderer_ES_3_0_Func = &OGLCreateRenderer_ES_3_0; +// GPU3DInterface *core3DList[] = { &gpu3DNull, &gpu3DRasterize, &gpu3Dgl_ES_3_0, NULL }; +// GPU->Set3DRendererByID(2); + +void OGLLoadEntryPoints_ES_3_0(); +void OGLCreateRenderer_ES_3_0(OpenGLRenderer **rendererPtr); + +class OpenGLESRenderer_3_0 : public OpenGLRenderer_3_2 +{ +protected: + virtual Render3DError CreateGeometryPrograms(); + virtual Render3DError CreateGeometryZeroDstAlphaProgram(const char *vtxShaderCString, const char *fragShaderCString); + virtual Render3DError CreateEdgeMarkProgram(const char *vtxShaderCString, const char *fragShaderCString); + virtual Render3DError CreateFogProgram(const OGLFogProgramKey fogProgramKey, const char *vtxShaderCString, const char *fragShaderCString); + virtual Render3DError CreateFramebufferOutput6665Program(const size_t outColorIndex, const char *vtxShaderCString, const char *fragShaderCString); + +public: + OpenGLESRenderer_3_0(); + + virtual Render3DError InitExtensions(); +}; + +#endif // OGLRENDER_ES3_H