Supermodel/Src/Graphics/Shaders2D.h
2024-03-06 02:11:21 -06:00

327 lines
9.1 KiB
C

/**
** 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 <http://www.gnu.org/licenses/>.
**/
/*
* 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 = (1 - (yCoord % 2)) * 16;
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] & 0x8000u) != 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] & uint(1 << (12 + layerNum))) != 0; }
bool LayerEnabled (int layerNum) { return (regs[0x60/4 + layerNum] & 0x80000000u) != 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
const uint mask = 0x1F;
vec4 c;
c.r = float((colour >> 0 ) & mask) / 31.0;
c.g = float((colour >> 5 ) & mask) / 31.0;
c.b = float((colour >> 10) & mask) / 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