/** ** Supermodel ** A Sega Model 3 Arcade Emulator. ** Copyright 2011-2012 Bart Trzynadlowski, Nik Henson ** ** This file is part of Supermodel. ** ** Supermodel 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 3 of the License, or (at your option) ** any later version. ** ** Supermodel 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 Supermodel. If not, see . **/ /* * Shaders2D.h * * Header file containing the 2D vertex and fragment shaders. */ #ifndef INCLUDED_SHADERS2D_H #define INCLUDED_SHADERS2D_H // Vertex shader static const char s_vertexShaderSource[] = R"glsl( #version 410 core // outputs out vec2 fsTexCoord; void main(void) { const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0), vec4(-1.0, 1.0, 0.0, 1.0), vec4( 1.0, -1.0, 0.0, 1.0), vec4( 1.0, 1.0, 0.0, 1.0)); fsTexCoord = ((vertices[gl_VertexID % 4].xy + 1.0) / 2.0); fsTexCoord.y = 1.0 - fsTexCoord.y; // flip upside down gl_Position = vertices[gl_VertexID % 4]; } )glsl"; // Fragment shader static const char s_fragmentShaderSource[] = R"glsl( #version 410 core // inputs uniform sampler2D tex1; // texture in vec2 fsTexCoord; // outputs out vec4 fragColor; void main() { fragColor = texture(tex1, fsTexCoord); } )glsl"; // Vertex shader static const char s_vertexShaderTileGen[] = R"glsl( #version 410 core uniform float lineStart; // defined as a % of the viewport height in the range 0-1. So 0 is top line, 0.5 is line 192 etc uniform float lineEnd; void main(void) { const float v1 = -1.0; const float v2 = 1.0; vec4 vertices[] = vec4[]( vec4(-1.0, v1, 0.0, 1.0), vec4(-1.0, v2, 0.0, 1.0), vec4( 1.0, v1, 0.0, 1.0), vec4( 1.0, v2, 0.0, 1.0)); float top = ((v2 - v1) * lineStart) + v1; float bottom = ((v2 - v1) * lineEnd ) + v1; vertices[0].y = top; vertices[2].y = top; vertices[1].y = bottom; vertices[3].y = bottom; gl_Position = vertices[gl_VertexID % 4]; } )glsl"; // Fragment shader static const char s_fragmentShaderTileGen[] = R"glsl( #version 410 core //layout(origin_upper_left) in vec4 gl_FragCoord; // inputs uniform usampler2D vram; // texture 512x512 uniform usampler2D palette; // texture 128x256 - actual dimensions dont matter too much but we have to stay in the limits of max tex width/height, so can't have 1 giant 1d array uniform uint regs[32]; uniform int layerNumber; // outputs out vec4 fragColor; ivec2 GetVRamCoords(int offset) { return ivec2(offset % 512, offset / 512); } ivec2 GetPaletteCoords(int offset) { return ivec2(offset % 128, offset / 128); } uint GetLineMask(int layerNum, int yCoord) { uint shift = (layerNum<2) ? 16u : 0u; // need to check this, we could be endian swapped so could be wrong uint maskPolarity = ((layerNum & 1) > 0) ? 0xFFFFu : 0x0000u; int index = (0xF7000 / 4) + yCoord; ivec2 coords = GetVRamCoords(index); uint mask = ((texelFetch(vram,coords,0).r >> shift) & 0xFFFFu) ^ maskPolarity; return mask; } bool GetPixelMask(int layerNum, int xCoord, int yCoord) { uint lineMask = GetLineMask(layerNum, yCoord); uint maskTest = 1 << (15-(xCoord/32)); return (lineMask & maskTest) != 0; } int GetLineScrollValue(int layerNum, int yCoord) { int index = ((0xF6000 + (layerNum * 0x400)) / 4) + (yCoord / 2); int shift = (yCoord % 2) * 16; // double check this ivec2 coords = GetVRamCoords(index); return int((texelFetch(vram,coords,0).r >> shift) & 0xFFFFu); } int GetTileNumber(int xCoord, int yCoord, int xScroll, int yScroll) { int xIndex = ((xCoord + xScroll) / 8) & 0x3F; int yIndex = ((yCoord + yScroll) / 8) & 0x3F; return (yIndex*64) + xIndex; } int GetTileData(int layerNum, int tileNumber) { int addressBase = (0xF8000 + (layerNum * 0x2000)) / 4; int offset = tileNumber / 2; // two tiles per 32bit word int shift = (1 - (tileNumber % 2)) * 16; // triple check this ivec2 coords = GetVRamCoords(addressBase+offset); uint data = (texelFetch(vram,coords,0).r >> shift) & 0xFFFFu; return int(data); } int GetVFine(int yCoord, int yScroll) { return (yCoord + yScroll) & 7; } int GetHFine(int xCoord, int xScroll) { return (xCoord + xScroll) & 7; } // register data bool LineScrollMode (int layerNum) { return (regs[0x60/4 + layerNum] & 0x8000) != 0; } int GetHorizontalScroll(int layerNum) { return int(regs[0x60 / 4 + layerNum] &0x3FFu); } int GetVerticalScroll (int layerNum) { return int((regs[0x60/4 + layerNum] >> 16) & 0x1FFu); } int LayerPriority () { return int((regs[0x20/4] >> 8) & 0xFu); } bool LayerIs4Bit (int layerNum) { return (regs[0x20/4] & (1 << (12 + layerNum))) != 0; } bool LayerEnabled (int layerNum) { return (regs[0x60/4 + layerNum] & 0x80000000) != 0; } bool LayerSelected (int layerNum) { return (LayerPriority() & (1 << layerNum)) == 0; } float Int8ToFloat(uint c) { if((c & 0x80u) > 0u) { // this is a bit harder in GLSL. Top bit means negative number, we extend to make 32bit return float(int(c | 0xFFFFFF00u)) / 128.0; } else { return float(c) / 127.0; } } vec4 AddColourOffset(int layerNum, vec4 colour) { uint offsetReg = regs[(0x40/4) + layerNum/2]; vec4 c; c.b = Int8ToFloat((offsetReg >>16) & 0xFFu); c.g = Int8ToFloat((offsetReg >> 8) & 0xFFu); c.r = Int8ToFloat((offsetReg >> 0) & 0xFFu); c.a = 0.0; colour += c; return clamp(colour,0.0,1.0); // clamp is probably not needed since will get clamped on render target } vec4 Int16ColourToVec4(uint colour) { uint alpha = (colour>>15); // top bit is alpha. 1 means clear, 0 opaque alpha = ~alpha; // invert alpha = alpha & 0x1u; // mask bit vec4 c; c.r = float((colour >> 0 ) & 0x1F) / 31.0; c.g = float((colour >> 5 ) & 0x1F) / 31.0; c.b = float((colour >> 10) & 0x1F) / 31.0; c.a = float(alpha) / 1.0; c.rgb *= c.a; // multiply by alpha value, this will push transparent to black, no branch needed return c; } vec4 GetColour(int layerNum, int paletteOffset) { ivec2 coords = GetPaletteCoords(paletteOffset); uint colour = texelFetch(palette,coords,0).r; vec4 col = Int16ColourToVec4(colour); // each colour is only 16bits, but occupies 32bits return AddColourOffset(layerNum,col); // apply colour offsets from registers } vec4 Draw4Bit(int layerNum, int tileData, int hFine, int vFine) { // Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels (offset of tile pattern within VRAM) int patternOffset = ((tileData & 0x3FFF) << 1) | ((tileData >> 15) & 1); patternOffset *= 32; patternOffset /= 4; // Upper color bits; the lower 4 bits come from the tile pattern int paletteIndex = tileData & 0x7FF0; ivec2 coords = GetVRamCoords(patternOffset+vFine); uint pattern = texelFetch(vram,coords,0).r; pattern = (pattern >> ((7-hFine)*4)) & 0xFu; // get the pattern for our horizontal value return GetColour(layerNum, paletteIndex | int(pattern)); } vec4 Draw8Bit(int layerNum, int tileData, int hFine, int vFine) { // Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels int patternOffset = tileData & 0x3FFF; patternOffset *= 64; patternOffset /= 4; // Upper color bits int paletteIndex = tileData & 0x7F00; // each read is 4 pixels int offset = hFine / 4; ivec2 coords = GetVRamCoords(patternOffset+(vFine*2)+offset); // 8-bit pixels, each line is two words uint pattern = texelFetch(vram,coords,0).r; pattern = (pattern >> ((3-(hFine%4))*8)) & 0xFFu; // shift out the bits we want for this pixel return GetColour(layerNum, paletteIndex | int(pattern)); } void main() { ivec2 pos = ivec2(gl_FragCoord.xy); int scrollX; if(LineScrollMode(layerNumber)) { scrollX = GetLineScrollValue(layerNumber, pos.y); } else { scrollX = GetHorizontalScroll(layerNumber); } int scrollY = GetVerticalScroll(layerNumber); int tileNumber = GetTileNumber(pos.x,pos.y,scrollX,scrollY); int hFine = GetHFine(pos.x,scrollX); int vFine = GetVFine(pos.y,scrollY); bool pixelMask = GetPixelMask(layerNumber,pos.x,pos.y); if(pixelMask==true) { int tileData = GetTileData(layerNumber,tileNumber); if(LayerIs4Bit(layerNumber)) { fragColor = Draw4Bit(layerNumber,tileData,hFine,vFine); } else { fragColor = Draw8Bit(layerNumber,tileData,hFine,vFine); } } else { fragColor = vec4(0.0); } } )glsl"; #endif // INCLUDED_SHADERS2D_H