diff --git a/GNUmakefile b/GNUmakefile index c83d273f0..042823606 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -228,7 +228,7 @@ else endif endif ifeq (1,$(USE_OPENGL)) - engine_objs += voxmodel.cpp mdsprite.cpp tilepacker.cpp + engine_objs += glsurface.cpp voxmodel.cpp mdsprite.cpp tilepacker.cpp engine_deps += glad ifeq (1,$(POLYMER)) engine_objs += glbuild.cpp polymer.cpp diff --git a/source/build/include/glsurface.h b/source/build/include/glsurface.h new file mode 100644 index 000000000..dd04b06ec --- /dev/null +++ b/source/build/include/glsurface.h @@ -0,0 +1,41 @@ +/* + * glsurface.cpp + * A 32-bit rendering surface that can quickly blit 8-bit paletted buffers implemented in OpenGL. + * + * Copyright © 2018, Alex Dawson. All rights reserved. + */ + +#ifndef GLSURFACE_H_ +#define GLSURFACE_H_ + +#include "compat.h" +#include "palette.h" + +// Initialize the glsurface with the Software renderer's buffer resolution. +// If the Software renderer's resolution and the actual resolution don't match, +// glsurface will still render at the full size of the screen. +// If a surface already exists, glsurface_destroy() will be automatically called before re-initializing. +// Returns whether or not the glsurface could be successfully initialized. +bool glsurface_initialize(vec2_t inputBufferResolution); + +// Destroy an existing surface. +void glsurface_destroy(); + +// Sets the palette at paletteID to contain the byte buffer pointed to by pPalette. +// If the surface is not initialized, the function returns immediately. +void glsurface_setPalette(int32_t paletteID, void* pPalette); + +// Returns a pointer to the start of the surface's pixel buffer +// Returns NULL if the surface is not initialized. +void* glsurface_getBuffer(); + +// Returns the resolution of the surface's buffer +vec2_t glsurface_getBufferResolution(); + +// Blit the surface's pixel buffer to the screen. +// paletteID is the id of a palette previously set with glsurface_setPalette(). +// Renders as soon as the data has been uploaded. +// If the surface is not initialized, the function returns immediately. +void glsurface_blitBuffer(int32_t paletteID); + +#endif /* GLSURFACE_H_ */ diff --git a/source/build/src/engine.cpp b/source/build/src/engine.cpp index cdce1377f..eeaacbbd5 100644 --- a/source/build/src/engine.cpp +++ b/source/build/src/engine.cpp @@ -23,6 +23,7 @@ #ifdef USE_OPENGL # include "glad/glad.h" +# include "glsurface.h" # include "mdsprite.h" # ifdef POLYMER # include "polymer.h" @@ -9898,6 +9899,11 @@ int32_t videoSetGameMode(char davidoption, int32_t daxdim, int32_t daydim, int32 #ifdef USE_OPENGL fxdim = (float) daxdim; fydim = (float) daydim; + + if (videoGetRenderMode() == REND_CLASSIC && !nogl) + { + glsurface_initialize({xdim, ydim}); + } #endif videoAllocateBuffers(); diff --git a/source/build/src/glsurface.cpp b/source/build/src/glsurface.cpp new file mode 100644 index 000000000..2d2ce014e --- /dev/null +++ b/source/build/src/glsurface.cpp @@ -0,0 +1,238 @@ +/* + * glsurface.cpp + * A 32-bit rendering surface that can quickly blit 8-bit paletted buffers implemented in OpenGL. + * + * Copyright © 2018, Alex Dawson. All rights reserved. + */ + +#include "glsurface.h" +#include "glad/glad.h" + +#include "baselayer.h" +#include "build.h" + +static void* buffer; +static GLuint bufferTexID; +static vec2_t bufferRes; + +static GLuint paletteTexIDs[MAXBASEPALS]; + +static GLuint quadVertsID = 0; + +static GLuint shaderProgramID = 0; +static GLint texSamplerLoc = -1; +static GLint paletteSamplerLoc = -1; + +static GLuint compileShader(GLenum shaderType, const char* const source) +{ + GLuint shaderID = glCreateShader(shaderType); + if (shaderID == 0) + return 0; + + const char* const sources[1] = {source}; + glShaderSource(shaderID, + 1, + sources, + NULL); + glCompileShader(shaderID); + + GLint compileStatus; + glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compileStatus); + OSD_Printf("Compile Status: %u\n", compileStatus); + + if (!compileStatus) + { + GLint logLength; + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) + { + char *infoLog = (char*) malloc(logLength); + glGetShaderInfoLog(shaderID, logLength, &logLength, infoLog); + OSD_Printf("Log:\n%s\n", infoLog); + free(infoLog); + } + } + + return shaderID; +} + +bool glsurface_initialize(vec2_t inputBufferResolution) +{ + if (buffer) + glsurface_destroy(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + bufferRes = inputBufferResolution; + buffer = malloc(bufferRes.x*bufferRes.y); + + glGenBuffers(1, &quadVertsID); + glBindBuffer(GL_ARRAY_BUFFER, quadVertsID); + const float quadVerts[] = + { + -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, //top-left + -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, //bottom-left + 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, //top-right + 1.0f, -1.0f, 0.0f, 1.0f, 1.0f //bottom-right + }; + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW); + + //specify format/arrangement for vertex positions: + glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(float) * 5, 0); + //specify format/arrangement for vertex texture coords: + glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(float) * 5, (const void*) (sizeof(float) * 3)); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glGenTextures(1, &bufferTexID); + glBindTexture(GL_TEXTURE_2D, bufferTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + 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_MAX_ANISOTROPY_EXT, 1); + 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_RED, bufferRes.x, bufferRes.y, 0, GL_RED, GL_UNSIGNED_BYTE, 0); + + const char* const VERTEX_SHADER_CODE = + "#version 110\n\ + \n\ + attribute vec4 i_vertPos;\n\ + attribute vec2 i_texCoord;\n\ + \n\ + varying vec2 v_texCoord;\n\ + \n\ + void main()\n\ + {\n\ + gl_Position = i_vertPos;\n\ + v_texCoord = i_texCoord;\n\ + }\n"; + const char* const FRAGMENT_SHADER_CODE = + "#version 110\n\ + \n\ + //s_texture points to an indexed color texture\n\ + uniform sampler2D s_texture;\n\ + //s_palette is the palette texture\n\ + uniform sampler2D s_palette;\n\ + \n\ + varying vec2 v_texCoord;\n\ + \n\ + const float c_paletteScale = 255.0/256.0;\n\ + const float c_paletteOffset = 0.5/256.0;\n\ + \n\ + void main()\n\ + {\n\ + vec4 color = texture2D(s_texture, v_texCoord.xy);\n\ + color.r = c_paletteOffset + c_paletteScale*color.r;\n\ + color = texture2D(s_palette, color.rg);\n\ + \n\ + // DEBUG \n\ + //color = texture2D(s_palette, v_texCoord.xy);\n\ + //color = texture2D(s_texture, v_texCoord.xy);\n\ + \n\ + gl_FragColor = color;\n\ + }\n"; + + shaderProgramID = glCreateProgram(); + GLuint vertexShaderID = compileShader(GL_VERTEX_SHADER, VERTEX_SHADER_CODE); + GLuint fragmentShaderID = compileShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE); + glAttachShader(shaderProgramID, vertexShaderID); + glAttachShader(shaderProgramID, fragmentShaderID); + glBindAttribLocation(shaderProgramID, 0, "i_vertPos"); + glBindAttribLocation(shaderProgramID, 1, "i_texCoord"); + glLinkProgram(shaderProgramID); + glDetachShader(shaderProgramID, vertexShaderID); + glDeleteShader(vertexShaderID); + glDetachShader(shaderProgramID, fragmentShaderID); + glDeleteShader(fragmentShaderID); + glUseProgram(shaderProgramID); + + texSamplerLoc = glGetUniformLocation(shaderProgramID, "s_texture"); + paletteSamplerLoc = glGetUniformLocation(shaderProgramID, "s_palette"); + + glUniform1i(texSamplerLoc, 0); + glUniform1i(paletteSamplerLoc, 1); + + for (int basepalnum = 0; basepalnum < MAXBASEPALS; ++basepalnum) + { + glsurface_setPalette(basepalnum, basepaltable[basepalnum]); + } + + return true; +} + +void glsurface_destroy() +{ + if (!buffer) + return; + + free(buffer); + buffer = 0; + + glDeleteBuffers(1, &quadVertsID); + quadVertsID = 0; + + glDeleteTextures(1, &bufferTexID); + bufferTexID = 0; + glDeleteTextures(MAXBASEPALS, paletteTexIDs); + memset(paletteTexIDs, 0, sizeof(paletteTexIDs)); + + glDeleteProgram(shaderProgramID); + shaderProgramID = 0; +} + +void glsurface_setPalette(int32_t paletteID, void* pPalette) +{ + if (!buffer) + return; + if (!pPalette) + return; + + glActiveTexture(GL_TEXTURE1); + if (paletteTexIDs[paletteID]) + { + glBindTexture(GL_TEXTURE_2D, paletteTexIDs[paletteID]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_RGB, GL_UNSIGNED_BYTE, (void*) buffer); + } + else + { + glGenTextures(1, paletteTexIDs+paletteID); + glBindTexture(GL_TEXTURE_2D, paletteTexIDs[paletteID]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + 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_MAX_ANISOTROPY_EXT, 1); + 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_RGB8, 256, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, pPalette); + } +} + +void* glsurface_getBuffer() +{ + return buffer; +} + +vec2_t glsurface_getBufferResolution() +{ + return bufferRes; +} + +void glsurface_blitBuffer(int32_t paletteID) +{ + if (!buffer) + return; + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, paletteTexIDs[paletteID]); + + glActiveTexture(GL_TEXTURE0); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bufferRes.x, bufferRes.y, GL_RED, GL_UNSIGNED_BYTE, (void*) buffer); + + glDrawArrays(GL_TRIANGLE_STRIP, + 0, + 4); +} diff --git a/source/build/src/palette.cpp b/source/build/src/palette.cpp index 2df48179f..6d2ab961f 100644 --- a/source/build/src/palette.cpp +++ b/source/build/src/palette.cpp @@ -8,6 +8,10 @@ #include "a.h" #include "xxhash.h" +#ifdef USE_OPENGL +# include "glsurface.h" +#endif + uint8_t *basepaltable[MAXBASEPALS] = { palette }; uint8_t basepalreset=1; uint8_t curbasepal; @@ -656,7 +660,14 @@ void paletteSetColorTable(int32_t id, uint8_t const * const table) Bmemcpy(basepaltable[id], table, 768); #ifdef USE_OPENGL - uploadbasepalette(id); + if (videoGetRenderMode() == REND_CLASSIC) + { + glsurface_setPalette(id, basepaltable[id]); + } + else if (videoGetRenderMode() >= REND_POLYMOST) + { + uploadbasepalette(id); + } #endif } diff --git a/source/build/src/sdlayer.cpp b/source/build/src/sdlayer.cpp index 7843692e5..dd6fe3275 100644 --- a/source/build/src/sdlayer.cpp +++ b/source/build/src/sdlayer.cpp @@ -16,6 +16,7 @@ #ifdef USE_OPENGL # include "glad/glad.h" # include "glbuild.h" +# include "glsurface.h" #endif #if defined _WIN32 @@ -1300,6 +1301,7 @@ static void destroy_window_resources() #ifdef USE_OPENGL void sdlayer_setvideomode_opengl(void) { + glsurface_destroy(); polymost_glreset(); glEnable(GL_TEXTURE_2D); @@ -1447,8 +1449,13 @@ int32_t setvideomode_sdlcommon(int32_t *x, int32_t *y, int32_t c, int32_t fs, in while (lockcount) videoEndDrawing(); #ifdef USE_OPENGL - if (bpp > 8 && sdl_surface) - polymost_glreset(); + if (sdl_surface) + { + if (bpp > 8) + polymost_glreset(); + else if (!nogl) + glsurface_destroy(); + } #endif // clear last gamma/contrast/brightness so that it will be set anew @@ -1462,7 +1469,7 @@ void setvideomode_sdlcommonpost(int32_t x, int32_t y, int32_t c, int32_t fs, int wm_setapptitle(apptitle); #ifdef USE_OPENGL - if (c > 8) + if (!nogl) sdlayer_setvideomode_opengl(); #endif @@ -1587,7 +1594,7 @@ int32_t videoSetMode(int32_t x, int32_t y, int32_t c, int32_t fs) initprintf("Setting video mode %dx%d (%d-bpp %s)\n", x, y, c, ((fs & 1) ? "fullscreen" : "windowed")); #ifdef USE_OPENGL - if (c > 8) + if (c > 8 || !nogl) { int32_t i, j; #ifdef USE_GLEXT @@ -1722,6 +1729,19 @@ void videoBeginDrawing(void) modechange = 0; return; } + else if (!nogl) + { + if (offscreenrendering) return; + + frameplace = (intptr_t)glsurface_getBuffer(); + if (modechange) + { + bytesperline = xdim; + calc_ylookup(bytesperline, ydim); + modechange=0; + } + return; + } // lock the frame if (lockcount++ > 0) @@ -1748,7 +1768,7 @@ void videoBeginDrawing(void) // void videoEndDrawing(void) { - if (bpp > 8) + if (bpp > 8 || !nogl) { if (!offscreenrendering) frameplace = 0; return; @@ -1783,14 +1803,21 @@ void videoShowFrame(int32_t w) #endif #ifdef USE_OPENGL - if (bpp > 8) + if (!nogl) { - if (palfadedelta) - fullscreen_tint_gl(palfadergb.r, palfadergb.g, palfadergb.b, palfadedelta); + if (bpp > 8) + { + if (palfadedelta) + fullscreen_tint_gl(palfadergb.r, palfadergb.g, palfadergb.b, palfadedelta); #ifdef __ANDROID__ - AndroidDrawControls(); + AndroidDrawControls(); #endif + } + else + { + glsurface_blitBuffer(curbasepal); + } static uint32_t lastSwapTime = 0; SDL_GL_SwapWindow(sdl_window);