Supermodel/Src/Graphics/Render2D.cpp

897 lines
28 KiB
C++

/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 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/>.
**/
/*
* Render2D.cpp
*
* Implementation of the CRender2D class: OpenGL tile generator graphics.
*
* To-Do List
* ----------
* - Add dirty rectangles? There is already some inactive code in here for
* this purpose and it needs to be updated or deleted once and for all.
* - Are v-scroll values 9 or 10 bits?
* - Add fast paths for no scrolling (including unclipped tile rendering).
* - Inline the loops in the tile renderers.
* - Update description of tile generator before you forget :)
* - A proper shut-down function is needed! OpenGL might not be available when
* the destructor for this class is called.
*/
#include <string.h>
#include "Pkgs/glew.h"
#include "Supermodel.h"
#include "Graphics/Shaders2D.h" // fragment and vertex shaders
/******************************************************************************
Definitions and Constants
******************************************************************************/
// Shader program files (for use in development builds only)
#define VERTEX_2D_SHADER_FILE "Src/Graphics/Vertex2D.glsl"
#define FRAGMENT_2D_SHADER_FILE "Src/Graphics/Fragment2D.glsl"
/******************************************************************************
Tile Drawing Functions
******************************************************************************/
// Draw 4-bit tile line, no clipping performed
void CRender2D::DrawTileLine4BitNoClip(UINT32 *buf, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
*buf++ = pal[((pattern>>28)&0xF) | palette];
*buf++ = pal[((pattern>>24)&0xF) | palette];
*buf++ = pal[((pattern>>20)&0xF) | palette];
*buf++ = pal[((pattern>>16)&0xF) | palette];
*buf++ = pal[((pattern>>12)&0xF) | palette];
*buf++ = pal[((pattern>>8)&0xF) | palette];
*buf++ = pal[((pattern>>4)&0xF) | palette];
*buf++ = pal[((pattern>>0)&0xF) | palette];
}
// Draw 8-bit tile line, clipped at left edge
void CRender2D::DrawTileLine8BitNoClip(UINT32 *buf, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
*buf++ = pal[((pattern>>24)&0xFF) | palette];
*buf++ = pal[((pattern>>16)&0xFF) | palette];
*buf++ = pal[((pattern>>8)&0xFF) | palette];
*buf++ = pal[((pattern>>0)&0xFF) | palette];
pattern = vram[tileOffset+tileLine+1];
*buf++ = pal[((pattern>>24)&0xFF) | palette];
*buf++ = pal[((pattern>>16)&0xFF) | palette];
*buf++ = pal[((pattern>>8)&0xFF) | palette];
*buf++ = pal[((pattern>>0)&0xFF) | palette];
}
// Draw 4-bit tile line, clipped at left edge
void CRender2D::DrawTileLine4Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
for (int bitPos = 28; bitPos >= 0; bitPos -= 4)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xF) | palette];
++offset;
}
}
// Draw 4-bit tile line, clipped at right edge
void CRender2D::DrawTileLine4BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 8 pattern pixels fetched at once
int bitPos;
// Tile pattern offset: each tile occupies 32 bytes when using 4-bit pixels
tileOffset = ((tile&0x3FFF)<<1) | ((tile>>15)&1);
tileOffset *= 32;
tileOffset /= 4; // VRAM is a UINT32 array
// Upper color bits; the lower 4 bits come from the tile pattern
palette = tile&0x7FF0;
// Draw 8 pixels
pattern = vram[tileOffset+tileLine];
bitPos = 28;
for (int i = 0; i < numPixels; i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xF) | palette];
++offset;
bitPos -= 4;
}
}
// Draw 8-bit tile line, clipped at left edge
void CRender2D::DrawTileLine8Bit(UINT32 *buf, int offset, UINT16 tile, int tileLine)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
for (int bitPos = 24; bitPos >= 0; bitPos -= 8)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
}
pattern = vram[tileOffset+tileLine+1];
for (int bitPos = 24; bitPos >= 0; bitPos -= 8)
{
if (offset >= 0)
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
}
}
// Draw 8-bit tile line, clipped at right edge
void CRender2D::DrawTileLine8BitRightClip(UINT32 *buf, int offset, UINT16 tile, int tileLine, int numPixels)
{
unsigned tileOffset; // offset of tile pattern within VRAM
unsigned palette; // color palette bits obtained from tile
UINT32 pattern; // 4 pattern pixels fetched at once
int bitPos;
tileLine *= 2; // 8-bit pixels, each line is two words
// Tile pattern offset: each tile occupies 64 bytes when using 8-bit pixels
tileOffset = tile&0x3FFF;
tileOffset *= 64;
tileOffset /= 4;
// Upper color bits
palette = tile&0x7F00;
// Draw 4 pixels at a time
pattern = vram[tileOffset+tileLine];
bitPos = 24;
for (int i = 0; (i < 4) && (i < numPixels); i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
bitPos -= 8;
}
pattern = vram[tileOffset+tileLine+1];
bitPos = 24;
for (int i = 0; (i < 4) && (i < numPixels); i++)
{
buf[offset] = pal[((pattern>>bitPos)&0xFF) | palette];
++offset;
bitPos -= 8;
}
}
/******************************************************************************
Layer Rendering
******************************************************************************/
/*
* DrawCompleteLayer():
*
* Updates the complete layer.
*/
void CRender2D::DrawCompleteLayer(int layerNum, const UINT16 *nameTableBase)
{
UINT32 *dest = surf; // destination surface to write to
UINT32 *lineBufferPri = &surf[512*496]; // line buffer for primary and alternate layer
UINT32 *lineBufferAlt = &surf[512*497];
UINT32 *buf;
const UINT16 *maskTable; // pointer to start of mask table
const UINT16 *hScrollTablePri, *hScrollTableAlt; // pointers to line scroll tables
const UINT16 *nameTablePri = nameTableBase; // primary (this layer) name table
const UINT16 *nameTableAlt = &nameTableBase[64*64]; // alternate layer's name table
const UINT16 *nameTable;
int colorDepthPri, colorDepthAlt; // primary and alternate layer color depths
int hScrollPri, hScrollAlt; // primary and alternate layer scroll offsets
int vScrollPri, vScrollAlt;
int hFullScrollPri, hFullScrollAlt; // full-screen horizontal scroll values (from registers)
int vOffset; // vertical pixel offset within tile
int tx, i, j;
bool lineScrollPri, lineScrollAlt; // line scrolling enable/disable
UINT16 mask;
// Determine layer color depths (1 if 4-bit, 0 if 8-bit)
colorDepthPri = regs[0x20/4] & (1<<(12+layerNum*2));
colorDepthAlt = regs[0x20/4] & (1<<(12+layerNum*2+1));
// Line scroll tables
hScrollTablePri = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400)/4];
hScrollTableAlt = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400+0x400)/4];
// Get correct offset into mask table
maskTable = (UINT16 *) &vram[0xF7000/4];
if (layerNum == 0)
++maskTable; // little endian, layer 0 is second word in each pair
// Load horizontal full-screen scroll values and scroll mode
hFullScrollPri = regs[0x60/4+layerNum*2]&0x3FF;
hFullScrollAlt = regs[0x60/4+layerNum*2+1]&0x3FF;
lineScrollPri = regs[0x60/4+layerNum*2]&0x8000;
lineScrollAlt = regs[0x60/4+layerNum*2+1]&0x8000;
// Load vertical scroll values
vScrollPri = (regs[0x60/4+layerNum*2]>>16)&0x1FF;
vScrollAlt = (regs[0x60/4+layerNum*2+1]>>16)&0x1FF;
// Iterate over all displayed lines
for (int y = 0; y < 384; y++)
{
/*
* Draw all tiles from primary layer first. Horizontal scrolling is not
* applied yet, but vertical scrolling is taken into account. An entire
* 512-pixel line is rendered so that it can be scrolled during mixing.
*/
nameTable = &nameTablePri[(64*((y+vScrollPri)/8)) & 0xFFF]; // clamp to 64x64=0x1000
vOffset = (y+vScrollPri)&7;
buf = lineBufferPri; // output to primary line buffer
for (tx = 0; tx < 64; tx += 4) // 4 tiles at a time (for masking)
{
if (colorDepthPri) //TODO: move this test outside of loop
{
DrawTileLine4BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
else
{
DrawTileLine8BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
// Next set of 4 tiles
nameTable += 4;
}
/*
* Draw the alternate layer wherever the primary layer was masked
*/
nameTable = &nameTableAlt[(64*((y+vScrollAlt)/8))&0xFFF];
vOffset = (y+vScrollAlt)&7;
buf = lineBufferAlt; // output to alternate line buffer
for (tx = 0; tx < 64; tx += 4) // 4 tiles at a time (for masking)
{
if (colorDepthAlt) //TODO: move this test outside of loop
{
DrawTileLine4BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine4BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
else
{
DrawTileLine8BitNoClip(buf, nameTable[1], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[0], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[3], vOffset);
buf += 8;
DrawTileLine8BitNoClip(buf, nameTable[2], vOffset);
buf += 8;
}
// Next set of 4 tiles
nameTable += 4;
}
/*
* Mix the two layers into the current line under control of the
* stencil mask, applying scrolling in the process.
*/
// Load horizontal scroll values
if (lineScrollPri)
hScrollPri = hScrollTablePri[y];
else
hScrollPri = hFullScrollPri;
if (lineScrollAlt)
hScrollAlt = hScrollTableAlt[y];
else
hScrollAlt = hFullScrollAlt;
// Mix first 60 tiles (4 at a time)
mask = *maskTable;
i = hScrollPri&511; // primary line index
j = hScrollAlt&511; // alternate line index
for (tx = 0; tx < 60; tx += 4)
{
if ((mask&0x8000)) // copy tiles from primary layer
{
if (i <= (512-32)) // safe to use memcpy for fast blit?
{
memcpy(dest, &lineBufferPri[i], 32*sizeof(UINT32));
i += 32;
dest += 32;
}
else // slow copy, wrap line boundary
{
for (int k = 0; k < 32; k++)
{
i &= 511;
*dest++ = lineBufferPri[i++];
}
}
j += 32; // update alternate pointer as well
}
else // copy tiles from alternate layer
{
if (j <= (512-32))
{
memcpy(dest, &lineBufferAlt[j], 32*sizeof(UINT32));
j += 32;
dest += 32;
}
else
{
for (int k = 0; k < 32; k++)
{
j &= 511;
*dest++ = lineBufferAlt[j++];
}
}
i += 32; // update primary
}
mask <<= 1;
}
// Mix last two tiles
if ((mask&0x8000)) // copy tiles from primary layer
{
for (int k = 0; k < 16; k++)
{
i &= 511;
*dest++ = lineBufferPri[i++];
}
}
else // copy from alternate
{
for (int k = 0; k < 16; k++)
{
j &= 511;
*dest++ = lineBufferAlt[j++];
}
}
// Next line
maskTable += 2; // next mask line
}
// Upload
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 496, 384, GL_RGBA, GL_UNSIGNED_BYTE, surf);
}
/*
* DrawRect():
*
* Draws a rectangular portion of the given layer and uploads it. Scrolling is
* applied but the result must be clipped against the rectangular window
* defined here by tileX, tileY, tileW, and tileH.
*
* Clipping for the right side is not checked, which will always work as long
* as the X and Y tile positions never exceed 61 and 47, respectively (the
* dimensions of the physical display; there is border space because the
* layers are 64x64).
*
* Parameters:
* layerNum Layer number (0 or 1).
* nameTableBase Pointer to the layer's name table.
* tileX Position of upper-left corner of rectangle on the
* screen, in units of 8-pixel tiles (0-61).
* tileY "" (0-47)
* tileW Width of rectangular region in tiles.
* tileH "" (height)
*/
void CRender2D::DrawRect(int layerNum, const UINT16 *nameTableBase, int tileX, int tileY, int tileW, int tileH)
{
UINT32 *dest = surf; // destination surface to write to
const UINT16 *maskTable; // pointer to start of mask table
const UINT16 *hScrollTablePri, *hScrollTableAlt; // pointers to line scroll tables
const UINT16 *nameTablePri = nameTableBase; // primary (this layer) name table
const UINT16 *nameTableAlt = &nameTableBase[64*64]; // alternate layer's name table
const UINT16 *nameTable;
int colorDepthPri, colorDepthAlt; // primary and alternate layer color depths
int hScrollPri, hScrollAlt; // primary and alternate layer scroll offsets
int vScrollPri, vScrollAlt;
int hFullScrollPri, hFullScrollAlt; // full-screen horizontal scroll values (from registers)
int hOffset, vOffset; // pixel offsets
int ntOffset; // offset in name table
int tx;
bool lineScrollPri, lineScrollAlt; // line scrolling enable/disable
UINT16 mask;
// Determine layer color depths (1 if 4-bit, 0 if 8-bit)
colorDepthPri = regs[0x20/4] & (1<<(12+layerNum*2));
colorDepthAlt = regs[0x20/4] & (1<<(12+layerNum*2+1));
// Line scroll tables
hScrollTablePri = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400)/4];
hScrollTableAlt = (UINT16 *) &vram[(0xF6000+layerNum*2*0x400+0x400)/4];
// Get correct offset into mask table
maskTable = (UINT16 *) &vram[0xF7000/4];
if (layerNum == 0)
++maskTable; // little endian, layer 0 is second word in each pair
// Load horizontal full-screen scroll values and scroll mode
hFullScrollPri = regs[0x60/4+layerNum*2]&0x3FF;
hFullScrollAlt = regs[0x60/4+layerNum*2+1]&0x3FF;
lineScrollPri = regs[0x60/4+layerNum*2]&0x8000;
lineScrollAlt = regs[0x60/4+layerNum*2+1]&0x8000;
// Load vertical scroll values
vScrollPri = (regs[0x60/4+layerNum*2]>>16)&0x3FF;
vScrollAlt = (regs[0x60/4+layerNum*2+1]>>16)&0x3FF;
// Iterate over actual line on screen
for (int y = tileY*8; y < (tileY+tileH)*8; y++)
{
// Load horizontal scroll values
if (lineScrollPri)
hScrollPri = hScrollTablePri[y];
else
hScrollPri = hFullScrollPri;
if (lineScrollAlt)
hScrollAlt = hScrollTableAlt[y];
else
hScrollAlt = hFullScrollAlt;
/*
* Draw all tiles from primary layer first
*/
// Compute scroll offsets into name table and destination
hOffset = -(hScrollPri&7);
vOffset = (y+vScrollPri)&7;
ntOffset = tileX+hScrollPri/8;
// Advance name table to our line (prior to h-scrolling)
nameTable = &nameTablePri[64*((y+vScrollPri)/8) & 0xFFF]; // clamp to 64x64=0x1000
// Each bit in the mask table corresponds to 4 tiles
mask = maskTable[y*2];
// Render a line!
//TODO: add one if scrolling
for (tx = 0; tx < tileW; tx++)
{
if ( ((mask<<((tileX+tx)/4)) & 0x8000) )
{
if (colorDepthPri)
DrawTileLine4Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset); // make sure ^1 belongs inside parenthesis...
else
DrawTileLine8Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
}
hOffset += 8;
ntOffset++;
}
// When scrolling, extra tile must be rendered at right edge of region
if ( ((mask<<((tileX+tx)/4)) & 0x8000) ) // re-use the last mask bit (mask doesn't scroll)
{
if (colorDepthPri)
DrawTileLine4BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollPri&7);
else
DrawTileLine8BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollPri&7);
}
/*
* Draw the alternate layer wherever the primary layer was masked
*/
hOffset = -(hScrollAlt&7);
vOffset = (y+vScrollAlt)&7;
ntOffset = tileX+hScrollAlt/8;
nameTable = &nameTableAlt[64*((y+vScrollAlt)/8)];
mask = maskTable[y*2];
for (tx = 0; tx < tileW; tx++)
{
if (0 == ((mask<<((tileX+tx)/4)) & 0x8000))
{
if (colorDepthAlt)
DrawTileLine4Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
else
DrawTileLine8Bit(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset);
}
hOffset += 8;
ntOffset++;
}
// When scrolling, extra tile must be rendered at right edge of region
if (0 == ((mask<<((tileX+tx)/4)) & 0x8000)) // re-use the last mask bit (mask doesn't scroll)
{
if (colorDepthAlt)
DrawTileLine4BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollAlt&7);
else
DrawTileLine8BitRightClip(dest, hOffset, nameTable[(ntOffset^1)&63], vOffset, hScrollAlt&7);
}
// Next line
dest += tileW*8; // image surface is only as wide as the rectangle we're updating
}
// Upload
glTexSubImage2D(GL_TEXTURE_2D, 0, tileX*8, tileY*8, tileW*8, tileH*8, GL_RGBA, GL_UNSIGNED_BYTE, surf);
}
// Updates any changed portions of a layer
void CRender2D::UpdateLayer(int layerNum)
{
glBindTexture(GL_TEXTURE_2D, texID[layerNum]);
allDirty = true;
if (allDirty)
{
// If everything is dirty, update the whole thing at once
DrawCompleteLayer(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4]);
//DrawRect(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4], 0, 0, 62, 48);
memset(dirty, 0, sizeof(dirty));
}
else
{
// Otherwise, for now, use a dumb approach that updates each rectangle individually
for (int y = 0; y < 64/DIRTY_RECT_HEIGHT; y++)
{
for (int x = 0; x < 48/DIRTY_RECT_WIDTH; x++)
{
if (dirty[layerNum][y][x])
{
DrawRect(layerNum, (UINT16 *) &vram[(0xF8000+layerNum*2*0x2000)/4], x*DIRTY_RECT_WIDTH, y*DIRTY_RECT_HEIGHT, DIRTY_RECT_WIDTH, DIRTY_RECT_HEIGHT);
dirty[layerNum][y][x] = 0; // not dirty anymore
}
}
}
}
}
/******************************************************************************
Frame Display Functions
******************************************************************************/
// Draws a layer to the screen
void CRender2D::DisplayLayer(int layerNum, GLfloat z)
{
glBindTexture(GL_TEXTURE_2D, texID[layerNum]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f/512.0f, 0.0f); glVertex3f(0.0f, 0.0f, z);
glTexCoord2f(496.0f/512.0f, 0.0f); glVertex3f(1.0f, 0.0f, z);
glTexCoord2f(496.0f/512.0f, 384.0f/512.0f); glVertex3f(1.0f, 1.0f, z);
glTexCoord2f(0.0f/512.0f, 384.0f/512.0f); glVertex3f(0.0f, 1.0f, z);
glEnd();
}
// Set up viewport and OpenGL state for 2D rendering (sets up blending function but disables blending)
void CRender2D::Setup2D(void)
{
// Set up the viewport and orthogonal projection
glViewport(xOffs, yOffs, xPixels, yPixels);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, 1.0, 1.0, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Enable texture mapping and blending
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha of 1.0 is opaque, 0 is transparent
glDisable(GL_BLEND);
// Disable Z-buffering
glDisable(GL_DEPTH_TEST);
// Shader program
glUseProgram(shaderProgram);
}
// Convert color offset register data to RGB
void CRender2D::ColorOffset(GLfloat colorOffset[3], UINT32 reg)
{
INT8 ir, ig, ib;
ib = (reg>>16)&0xFF;
ig = (reg>>8)&0xFF;
ir = (reg>>0)&0xFF;
/*
* Uncertain how these should be interpreted. It appears to be signed,
* which means the values range from -128 to +127. The division by 128
* normalizes this to roughly -1,+1.
*/
colorOffset[0] = (GLfloat) ir * (1.0f/128.0f);
colorOffset[1] = (GLfloat) ig * (1.0f/128.0f);
colorOffset[2] = (GLfloat) ib * (1.0f/128.0f);
//printf("%08X -> %g,%g,%g\n", reg, colorOffset[2], colorOffset[1], colorOffset[0]);
}
// Bottom layers
void CRender2D::BeginFrame(void)
{
GLfloat colorOffset[3];
// Update all layers
for (int i = 0; i < 2; i++)
{
UpdateLayer(i);
}
allDirty = false;
// Draw bottom layer
Setup2D();
ColorOffset(colorOffset, regs[0x44/4]);
glUniform3fv(colorOffsetLoc, 1, colorOffset);
DisplayLayer(1, 0.0);
}
// Top layers
void CRender2D::EndFrame(void)
{
GLfloat colorOffset[3];
// Draw top layer
Setup2D();
glEnable(GL_BLEND);
ColorOffset(colorOffset, regs[0x40/4]);
glUniform3fv(colorOffsetLoc, 1, colorOffset);
DisplayLayer(0, -0.5);
}
/******************************************************************************
Emulation Callbacks
******************************************************************************/
void CRender2D::WritePalette(unsigned color, UINT32 data)
{
UINT8 r, g, b, a;
a = 0xFF * ((data>>15)&1); // decode the RGBA (make alpha 0xFF or 0x00)
a = ~a; // invert it (set on Model 3 means clear pixel)
if ((data&0x8000))
r = g = b = 0;
else
{
b = (data>>7)&0xF8;
g = (data>>2)&0xF8;
r = (data<<3)&0xF8;
}
pal[color] = (a<<24)|(b<<16)|(g<<8)|r;
}
void CRender2D::WriteVRAM(unsigned addr, UINT32 data)
{
if (vram[addr/4] == data) // do nothing if no changes
return;
// For now, mark everything as dirty
allDirty = true;
// Palette
if (addr >= 0x100000)
{
unsigned color = (addr-0x100000)/4; // color index
WritePalette(color, data);
}
}
/*
* InitPalette():
*
* This must be called from AttachVRAM() to initialize the palette. The reason
* is that because WriteVRAM() always compares incoming data to what is already
* in the VRAM, there is no actual way to initialize the palette by calling
* WriteVRAM() and passing it the initial VRAM contents. It will always fail to
* update because nothing is being changed.
*
* This function fixes the transparent pixel bug that frequently occurred when
* loading save states in Supermodel 0.1a.
*/
void CRender2D::InitPalette(void)
{
for (int i = 0; i < 0x20000/4; i++)
WritePalette(i, vram[0x100000/4 + i]);
}
/******************************************************************************
Configuration, Initialization, and Shutdown
******************************************************************************/
void CRender2D::AttachRegisters(const UINT32 *regPtr)
{
regs = regPtr;
DebugLog("Render2D attached registers\n");
}
void CRender2D::AttachVRAM(const UINT8 *vramPtr)
{
vram = (UINT32 *) vramPtr;
InitPalette();
DebugLog("Render2D attached VRAM\n");
}
#define MEMORY_POOL_SIZE (512*512*4+0x20000)
bool CRender2D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes)
{
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
// Load shaders
if (OKAY != LoadShaderProgram(&shaderProgram,&vertexShader,&fragmentShader,NULL,NULL,vertexShaderSource,fragmentShaderSource))
return FAIL;
// Get locations of the uniforms
glUseProgram(shaderProgram); // bind program
textureMapLoc = glGetUniformLocation(shaderProgram, "textureMap");
glUniform1i(textureMapLoc,0); // attach it to texture unit 0
colorOffsetLoc = glGetUniformLocation(shaderProgram, "colorOffset");
// Allocate memory for layer surfaces and palette
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
if (NULL == memoryPool)
return ErrorLog("Insufficient memory for tile layer surfaces (need %1.1f MB).", memSizeMB);
memset(memoryPool,0,MEMORY_POOL_SIZE);
// Set up pointers to memory regions
surf = (UINT32 *) memoryPool;
pal = (UINT32 *) &memoryPool[512*512*4];
// Resolution
xPixels = xRes;
yPixels = yRes;
xOffs = xOffset;
yOffs = yOffset;
// Clear textures and dirty rectangles (all memory)
memset(memoryPool, 0, MEMORY_POOL_SIZE);
memset(dirty, 0, sizeof(dirty));
allDirty = true;
// Create textures
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(2, texID);
for (int i = 0; i < 2; i++)
{
glBindTexture(GL_TEXTURE_2D, texID[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf);
if (glGetError() != GL_NO_ERROR)
return ErrorLog("OpenGL was unable to provide 512x512-texel texture maps for tile map layers.");
}
DebugLog("Render2D initialized (allocated %1.1f MB)\n", memSizeMB);
return OKAY;
}
CRender2D::CRender2D(void)
{
xPixels = 496;
yPixels = 384;
xOffs = 0;
yOffs = 0;
memoryPool = NULL;
vram = NULL;
surf = NULL;
DebugLog("Built Render2D\n");
}
CRender2D::~CRender2D(void)
{
DestroyShaderProgram(shaderProgram,vertexShader,fragmentShader);
glDeleteTextures(2, texID);
if (memoryPool != NULL)
{
delete [] memoryPool;
memoryPool = NULL;
}
surf = NULL;
vram = NULL;
DebugLog("Destroyed Render2D\n");
}