2011-04-24 01:14:00 +00:00
/**
* * Supermodel
* * A Sega Model 3 Arcade Emulator .
2020-12-20 18:17:34 +00:00
* * Copyright 2011 - 2012 Bart Trzynadlowski , Nik Henson
2011-04-24 01:14:00 +00:00
* *
* * This file is part of Supermodel .
* *
* * Supermodel is free software : you can redistribute it and / or modify it under
2020-12-20 18:17:34 +00:00
* * the terms of the GNU General Public License as published by the Free
2011-04-24 01:14:00 +00:00
* * 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/>.
* */
2020-12-20 18:17:34 +00:00
2011-04-24 01:14:00 +00:00
/*
* Render2D . cpp
2012-02-23 00:12:46 +00:00
*
2020-12-20 18:17:34 +00:00
* Implementation of the CRender2D class : OpenGL tile generator graphics .
2016-03-22 12:30:23 +00:00
*
2012-02-10 19:53:51 +00:00
* To - Do List
* - - - - - - - - - -
2016-05-08 21:17:02 +00:00
* - Is there a universal solution to the ' ROLLING START ' scrolling bug ( Scud
* Race ) and the scrolling text during Magical Truck Adventure ' s attract
* mode ? To fix Scud Race , either the stencil mask or the h - scroll value must
* be shifted by 16 pixels . Magical Truck Adventure is similar but opposite .
* Perhaps this is a function of timing registers accessed via JTAG ?
2020-12-20 18:17:34 +00:00
* - Is there a better way to handle the overscan regions in wide screen mode ?
2012-02-23 00:12:46 +00:00
* Is clearing two thin viewports better than one big clear ?
2012-02-10 19:53:51 +00:00
* - Are v - scroll values 9 or 10 bits ? ( Does it matter ? ) Lost World seems to
* have some scrolling issues .
* - A proper shut - down function is needed ! OpenGL might not be available when
* the destructor for this class is called .
*
2012-01-27 22:27:38 +00:00
* Tile Generator Hardware Overview
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Model 3 ' s medium resolution tile generator hardware appears to be derived
2012-02-10 19:53:51 +00:00
* from the Model 2 and System 24 chipset , but is much simpler . It consists of
2020-12-20 18:17:34 +00:00
* four 64 x64 tile layers , comprised of 8 x8 pixel tiles , with configurable
* priorities . There may be additional features but so far , no known Model 3
2012-02-10 19:53:51 +00:00
* games use them .
2012-01-27 22:27:38 +00:00
*
2020-12-20 18:17:34 +00:00
* VRAM is comprised of 1 MB for tile data and an additional 128 KB for the
2012-02-22 01:34:42 +00:00
* palette ( each color occupies 32 bits ) . The four tilemap layers are referred
2020-12-20 18:17:34 +00:00
* to as : A ( 0 ) , A ' ( 1 ) , B ( 2 ) , and B ' ( 3 ) . Palette RAM may be located on a
2012-02-22 01:34:42 +00:00
* separate RAM IC .
2012-01-27 22:27:38 +00:00
*
* Registers
* - - - - - - - - -
*
* Registers are listed by their byte offset in the PowerPC address space . Each
2020-12-20 18:17:34 +00:00
* is 32 bits wide and little endian . Only those registers relevant to
2012-01-27 22:27:38 +00:00
* rendering are listed here ( see CTileGen for others ) .
*
2016-05-07 21:54:03 +00:00
* Offset : Description :
2012-01-27 22:27:38 +00:00
*
2016-05-07 21:54:03 +00:00
* 0x20 Layer configuration
* 0x40 Layer A / A ' color offset
* 0x44 Layer B / B ' color offset
* 0x60 Layer A scroll
* 0x64 Layer A ' scroll
* 0x68 Layer B scroll
* 0x6C Layer B ' scroll
2012-01-27 22:27:38 +00:00
*
* Layer configuration is formatted as :
*
2016-05-07 21:54:03 +00:00
* 31 0
* ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pqrs tuvw ? ? ? ? ? ? ? ?
2012-01-27 22:27:38 +00:00
*
2020-12-20 18:17:34 +00:00
* Bits ' pqrs ' control the color depth of layers B ' , B , A ' , and A ,
2012-02-10 19:53:51 +00:00
* respectively . If set , the layer ' s pattern data is encoded as 4 bits ,
2020-12-20 18:17:34 +00:00
* otherwise the pixels are 8 bits .
2016-05-08 19:27:08 +00:00
*
* Bits ' tuvw ' control priority for layers B ' , B , A ' , and A , respectively ,
* which is also the relative ordering of the layers from bottom to top . For
* each layer , if its bit is clear , it will be drawn below the 3 D layer ,
* otherwise it is drawn on top .
2012-01-27 22:27:38 +00:00
*
* The remaining registers are described where appropriate further below .
*
* VRAM Memory Map
* - - - - - - - - - - - - - - -
*
* The lower 1 MB of VRAM is used for storing tiles , per - line horizontal scroll
* values , and the stencil mask , which determines which of each pair of layers
* is displayed on a given line and column .
*
2016-05-07 21:54:03 +00:00
* 00000 - F5FFF Tile pattern data
* F6000 - F63FF Layer A horizontal scroll table ( 512 lines )
2020-12-20 18:17:34 +00:00
* F6400 - F67FF Layer A ' horizontal scroll table
2016-05-07 21:54:03 +00:00
* F6800 - F6BFF Layer B horizontal scroll table
* F6C00 - F6FFF Layer B ' horizontal scroll table
* F7000 - F77FF Mask table ( assuming 4 bytes per line , 512 lines )
* F7800 - F7FFF ?
* F8000 - F9FFF Layer A name table
* FA000 - FBFFF Layer A ' name table
* FC000 - FDFFF Layer B name table
* FE000 - FFFFF Layer B ' name table
2012-01-27 22:27:38 +00:00
*
* Tiles may actually address the entire 1 MB space , although in practice ,
* that would conflict with the other fixed memory regions .
2020-12-20 18:17:34 +00:00
*
2012-01-27 22:27:38 +00:00
* Palette
* - - - - - - -
*
2012-02-22 01:34:42 +00:00
* The palette stores 32768 colors . Each entry is a little endian 32 - bit word .
* The upper 16 bits are unused and the lower 16 bits contain the color :
2012-01-27 22:27:38 +00:00
*
2016-05-07 21:54:03 +00:00
* 15 0
* tbbb bbgg gggr rrrr
2012-01-27 22:27:38 +00:00
*
* The ' t ' bit is for transparency . When set , pixels of that color are
* transparent , unless they are the bottom - most layer .
*
* Tile Name Table and Pattern Layout
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* The name table is a 64 x64 array of 16 - bit words serving as indices for tile
2020-12-20 18:17:34 +00:00
* pattern data and the palette . The first 64 words correspond to the first
2012-01-27 22:27:38 +00:00
* row of tiles , the next 64 to the second row , etc . Although 64 x64 entries
* describes a 512 x512 pixel screen , only the upper - left 62 x48 tiles are
2020-12-20 18:17:34 +00:00
* visible when the vertical and horizontal scroll values are 0. Scrolling
2012-01-27 22:27:38 +00:00
* moves the 496 x384 pixel ' window ' around , with individual wrapping of the
* two axes .
*
2020-12-20 18:17:34 +00:00
* The data is actually arranged in 32 - bit chunks in little endian format , so
* that tiles 0 , 1 , 2 , and 3 will be stored as 1 , 0 , 3 , 2. Fetching two name
* table entries as a single 32 - bit word places the left tile in the high 16
2012-01-27 22:27:38 +00:00
* bits and the right tile in the low 16 bits .
*
* The format of a name table entry in 4 - bit color mode is :
*
2016-05-07 21:54:03 +00:00
* 15 0
* jkpp pppp pppp iiii
2012-01-27 22:27:38 +00:00
*
* The pattern index is ' 0 ppp pppp pppi iiij ' . Multiplying by 32 yields the
2020-12-20 18:17:34 +00:00
* offset in VRAM at which the tile pattern data is stored . Note that the MSB
* of the name table entry becomes the LSB of the pattern index . This allows
* for 32768 4 - bit tile patterns , each occupying 32 bytes , which means the
2012-01-27 22:27:38 +00:00
* whole 1 MB VRAM space can be addressed .
*
* The 4 - bit pattern data is stored as 8 32 - bit words . Each word stores a row
* of 8 pixels :
*
2016-05-07 21:54:03 +00:00
* 31 0
* aaaa bbbb cccc dddd eeee ffff gggg hhhh
2012-01-27 22:27:38 +00:00
*
* ' a ' is the left - most pixel data . These 4 - bit values are combined with bits
* from the name table to form a palette index , which determines the final
* color . For example , for pixel ' a ' , the 15 - bit color index is :
2020-12-20 18:17:34 +00:00
*
2012-01-27 22:27:38 +00:00
* 14 0
2016-05-07 21:54:03 +00:00
* kpp pppp pppp aaaa
2012-01-27 22:27:38 +00:00
*
* Note that index bits are re - used to form the palette index , meaning that
* the pattern address partly determines the color .
*
* In 8 - bit color mode , the name table entry looks like :
*
2016-05-07 21:54:03 +00:00
* 15 0
* ? ppp pppp iiii iiii
2012-01-27 22:27:38 +00:00
*
2020-12-20 18:17:34 +00:00
* The low 15 ' p ' and ' i ' bits together form the pattern index , which must be
2012-01-27 22:27:38 +00:00
* multiplied by 64 to get the offset . The pattern data now consists of 16 32 -
* bit words , each containing four 8 - bit pixels :
*
2016-05-07 21:54:03 +00:00
* 31 0
* aaaa aaaa bbbb bbbb cccc cccc dddd dddd
2012-01-27 22:27:38 +00:00
*
* ' a ' is the left - most pixel . Each line is therefore comprised of two 32 - bit
* words . The palette index for pixel ' a ' is now formed from :
*
2016-05-07 21:54:03 +00:00
* 14 0
* ppp pppp aaaa aaaa
2012-01-27 22:27:38 +00:00
*
2020-12-20 18:17:34 +00:00
* Stencil Mask
2012-01-27 22:27:38 +00:00
* - - - - - - - - - - - -
*
* For any pixel position , there are in fact only two visible layers , despite
* there being four defined layers . The layers are grouped in pairs : A ( the
* ' primary ' layer ) and A ' ( the ' alternate ' ) form one pair , and B and B ' form
* the other . Only one of the primary or alternate layers from each group may
* be visible at a given position . The ' stencil mask ' controls this .
*
* The mask table is a bit field organized into 512 ( or 384 ? ) lines with each
* bit controlling four columns ( 32 pixels ) . The mask does not appear to be
* affected by scrolling - - that is , it does not scroll with the underlying
2016-05-08 21:17:02 +00:00
* tiles , which do so independently . The mask remains fixed .
2012-01-27 22:27:38 +00:00
*
* Each mask entry is a little endian 32 - bit word . The high 16 bits control
* A / A ' and the low 16 bits control B / B ' . Each word controls an entire line
2016-05-07 21:54:03 +00:00
* ( 32 pixels per bit , 512 pixels per 16 - bit line mask , where the first 16
* pixels are allocated to the overscan region . ) If a bit is set to 1 , the
* pixel from the primary layer is used , otherwise the alternate layer is
2012-01-27 22:27:38 +00:00
* used when the mask is 0. It is important to remember that the layers may
* have been scrolled independently . The mask operates on the final resultant
* two pixels that are determined for each location .
*
* Example of a line mask :
*
2016-05-07 21:54:03 +00:00
* 31 15 0
* 0111 0000 0000 1111 0000 0000 1111 1111
2012-01-27 22:27:38 +00:00
*
* These settings would display layer A ' for the first 32 pixels of the line ,
2020-12-20 18:17:34 +00:00
* followed by layer A for the next 96 pixels , A ' for the subsequent 256
2012-01-27 22:27:38 +00:00
* pixels , and A for the final 128 pixels . The first 256 pixels of the line
* would display layer B ' and the second 256 pixels would be from layer B .
*
2020-12-20 18:17:34 +00:00
* The stencil mask does not affect layer priorities , which are managed
2012-01-27 22:27:38 +00:00
* separately regardless of mask settings .
*
* Scrolling
* - - - - - - - - -
*
* Each of the four layers can be scrolled independently . Vertical scroll
* values are stored in the appropriate scroll register and horizontal scroll
* values can be sourced either from the register ( in which case the entire
* layer will be scrolled uniformly ) or from a table in VRAM ( which contains
2020-12-20 18:17:34 +00:00
* independent values for each line ) .
2012-01-27 22:27:38 +00:00
*
* The scroll registers are laid out as :
*
2016-05-07 21:54:03 +00:00
* 31 0
* e ? ? ? ? ? ? y yyyy yyyy h ? ? ? ? ? xx xxxx xxxx
2012-01-27 22:27:38 +00:00
*
2020-12-20 18:17:34 +00:00
* The ' e ' bit enables the layer when set . The ' y ' bits comprise a vertical
2016-05-07 21:54:03 +00:00
* scroll value in pixels . The ' x ' bits form a horizontal scroll value . If ' h '
* is set , then the VRAM table ( line - by - line scrolling ) is used , otherwise the
2020-12-20 18:17:34 +00:00
* ' x ' values are applied to every line . It is also possible that the scroll
2016-05-07 21:54:03 +00:00
* values use more or less bits , but probably no more than 1.
2012-01-27 22:27:38 +00:00
*
* Each line must be wrapped back to the beginning of the same line . Likewise ,
* vertical scrolling wraps around back to the top of the tilemap .
*
* The horizontal scroll table is a series of 16 - bit little endian words , one
* for each line beginning at 0. It appears all the values can be used for
* scrolling ( no control bits have been observed ) . The number of bits actually
2020-12-20 18:17:34 +00:00
* used by the hardware is irrelevant - - wrapping has the effect of making
2012-01-27 22:27:38 +00:00
* higher order bits unimportant .
*
* Layer Priorities
* - - - - - - - - - - - - - - - -
*
* The layer control register ( 0x20 ) contains 4 bits that appear to control
* layer priorities . It is assumed that the 3 D graphics , output by the Real3D
* pixel processors independently of the tile generator , constitute their own
* ' layer ' and that the 2 D tilemaps appear in front or behind . There may be a
* specific function for each priority bit or the field may be interpreted as a
* single 4 - bit value denoting preset layer orders .
*
* Color Offsets
* - - - - - - - - - - - - -
*
* Color offsets can be applied to the final RGB color value of every pixel .
* This is used for effects such as fading to a certain color , lightning ( Lost
* World ) , etc . The current best guess is that the two registers control each
* pair ( A / A ' and B / B ' ) of layers . The format appears to be :
*
2016-05-07 21:54:03 +00:00
* 31 0
* ? ? ? ? ? ? ? ? rrrr rrrr gggg gggg bbbb bbbb
2012-01-27 22:27:38 +00:00
*
* Where ' r ' , ' g ' , and ' b ' appear to be signed 8 - bit color offsets . Because
* they exceed the color resolution of the palette , they must be scaled
* appropriately .
2012-02-20 03:45:48 +00:00
*
* Color offset registers are handled in TileGen . cpp . Two palettes are computed
* - - one for A / A ' and another for B / B ' . These are passed to the renderer .
2011-04-24 01:14:00 +00:00
*/
2021-11-22 17:15:06 +00:00
# include "Render2D.h"
# include "Supermodel.h"
# include "Shader.h"
# include "Shaders2D.h" // fragment and vertex shaders
2012-02-20 03:45:48 +00:00
# include <cstring>
2020-07-31 19:18:51 +00:00
# include <GL/glew.h>
2011-04-24 01:14:00 +00:00
/******************************************************************************
Definitions and Constants
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Shader program files (for use in development builds only)
2016-05-07 21:54:03 +00:00
# define VERTEX_2D_SHADER_FILE "Src / Graphics / Vertex2D.glsl"
# define FRAGMENT_2D_SHADER_FILE "Src / Graphics / Fragment2D.glsl"
2011-04-24 01:14:00 +00:00
/******************************************************************************
2016-05-07 21:54:03 +00:00
Layer Rendering
This code is quite slow and badly needs to be optimized . Dirty rectangles
should be implemented first and tile pre - decoding second .
2011-04-24 01:14:00 +00:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2016-05-07 21:54:03 +00:00
template < int bits , bool alphaTest , bool clip >
static inline void DrawTileLine ( uint32_t * line , int pixelOffset , uint16_t tile , int patternLine , const uint32_t * vram , const uint32_t * palette , uint16_t mask )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
static_assert ( bits = = 4 | | bits = = 8 , " Tiles are either 4- or 8-bit " ) ;
// For 8-bit pixels, each line of tile pattern is two words
if ( bits = = 8 )
patternLine * = 2 ;
// Compute offset of pattern for this line
int patternOffset ;
if ( bits = = 4 )
{
patternOffset = ( ( tile & 0x3FFF ) < < 1 ) | ( ( tile > > 15 ) & 1 ) ;
patternOffset * = 32 ;
patternOffset / = 4 ;
}
else
{
patternOffset = tile & 0x3FFF ;
patternOffset * = 64 ;
patternOffset / = 4 ;
2020-12-20 18:17:34 +00:00
}
2016-05-07 21:54:03 +00:00
// Name table entry provides high color bits
uint32_t colorHi = tile & ( ( bits = = 4 ) ? 0x7FF0 : 0x7F00 ) ;
// Draw
if ( bits = = 4 )
{
uint32_t pattern = vram [ patternOffset + patternLine ] ;
for ( int p = 7 ; p > = 0 ; p - - )
{
2022-07-18 19:43:13 +00:00
if ( ! clip | | ( /*pixelOffset >= 0 &&*/ ( unsigned int ) pixelOffset < 496u ) ) // the >= 0 check is accounted for, as the cast to uint makes them appear as very large unsigned values
2016-05-07 21:54:03 +00:00
{
2016-05-08 21:17:02 +00:00
uint16_t maskTest = 1 < < ( 15 - ( ( pixelOffset + 0 ) / 32 ) ) ;
2016-05-07 21:54:03 +00:00
bool visible = ( mask & maskTest ) ! = 0 ;
2022-07-11 16:49:02 +00:00
uint32_t pixel = visible ? palette [ ( ( pattern > > ( p * 4 ) ) & 0xF ) | colorHi ] : 0 ;
if ( ! alphaTest | | ( visible & & ( pixel > > 24 ) ! = 0 ) ) // only draw opaque pixels
2016-05-07 21:54:03 +00:00
line [ pixelOffset ] = pixel ;
}
+ + pixelOffset ;
}
}
else
{
for ( int i = 0 ; i < 2 ; i + + ) // 4 pixels per word
{
uint32_t pattern = vram [ patternOffset + patternLine + i ] ;
for ( int p = 3 ; p > = 0 ; p - - )
{
2022-07-18 19:43:13 +00:00
if ( ! clip | | ( /*pixelOffset >= 0 &&*/ ( unsigned int ) pixelOffset < 496u ) ) // the >= 0 check is accounted for, as the cast to uint makes them appear as very large unsigned values
2016-05-07 21:54:03 +00:00
{
2016-05-08 21:17:02 +00:00
uint16_t maskTest = 1 < < ( 15 - ( ( pixelOffset + 0 ) / 32 ) ) ;
2016-05-07 21:54:03 +00:00
bool visible = ( mask & maskTest ) ! = 0 ;
2022-07-11 16:49:02 +00:00
uint32_t pixel = visible ? palette [ ( ( pattern > > ( p * 8 ) ) & 0xFF ) | colorHi ] : 0 ;
if ( ! alphaTest | | ( visible & & ( pixel > > 24 ) ! = 0 ) )
2016-05-07 21:54:03 +00:00
line [ pixelOffset ] = pixel ;
}
+ + pixelOffset ;
}
}
}
2011-04-24 01:14:00 +00:00
}
2016-05-07 21:54:03 +00:00
template < int bits , bool alphaTest >
static void DrawLayer ( uint32_t * pixels , int layerNum , const uint32_t * vram , const uint32_t * regs , const uint32_t * palette )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
const uint16_t * nameTableBase = ( const uint16_t * ) & vram [ ( 0xF8000 + layerNum * 0x2000 ) / 4 ] ;
const uint16_t * hScrollTable = ( const uint16_t * ) & vram [ ( 0xF6000 + layerNum * 0x400 ) / 4 ] ;
bool lineScrollMode = ( regs [ 0x60 / 4 + layerNum ] & 0x8000 ) ! = 0 ;
int hFullScroll = regs [ 0x60 / 4 + layerNum ] & 0x3FF ;
int vScroll = ( regs [ 0x60 / 4 + layerNum ] > > 16 ) & 0x1FF ;
const uint16_t * maskTable = ( const uint16_t * ) & vram [ 0xF7000 / 4 ] ;
if ( layerNum < 2 ) // little endian: layers A and A' use second word in each pair
maskTable + = 1 ;
// If mask bit is clear, alternate layer is shown. We want to test for non-
// zero, so we flip the mask when drawing alternate layers (layers 1 and 3).
const uint16_t maskPolarity = ( layerNum & 1 ) ? 0xFFFF : 0x0000 ;
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
uint32_t * line = pixels ;
for ( int y = 0 ; y < 384 ; y + + )
{
2016-05-08 21:17:02 +00:00
int hScroll = ( lineScrollMode ? hScrollTable [ y ] : hFullScroll ) & 0x1FF ;
2016-05-07 21:54:03 +00:00
int hTile = hScroll / 8 ;
int hFine = hScroll & 7 ; // horizontal pixel offset within tile line
int vFine = ( y + vScroll ) & 7 ; // vertical pixel offset within 8x8 tile
const uint16_t * nameTable = & nameTableBase [ ( 64 * ( ( y + vScroll ) / 8 ) ) & 0xFFF ] ; // clamp to 64x64 = 0x1000
uint16_t mask = * maskTable ^ maskPolarity ; // each bit covers 32 pixels
int pixelOffset = - hFine ;
int extraTile = ( hFine ! = 0 ) ? 1 : 0 ; // h-scrolling requires part of 63rd tile
2016-05-08 19:27:08 +00:00
// First tile may be clipped
2016-05-07 21:54:03 +00:00
DrawTileLine < bits , alphaTest , true > ( line , pixelOffset , nameTable [ ( hTile ^ 1 ) & 63 ] , vFine , vram , palette , mask ) ;
+ + hTile ;
pixelOffset + = 8 ;
2016-05-08 19:27:08 +00:00
// Middle tiles will not be clipped
2022-07-11 16:49:02 +00:00
for ( int tx = 1 ; tx < ( 62 - 1 + extraTile ) ; tx + + )
2016-05-07 21:54:03 +00:00
{
DrawTileLine < bits , alphaTest , false > ( line , pixelOffset , nameTable [ ( hTile ^ 1 ) & 63 ] , vFine , vram , palette , mask ) ;
+ + hTile ;
pixelOffset + = 8 ;
}
2016-05-08 19:27:08 +00:00
// Last tile may be clipped
2016-05-07 21:54:03 +00:00
DrawTileLine < bits , alphaTest , true > ( line , pixelOffset , nameTable [ ( hTile ^ 1 ) & 63 ] , vFine , vram , palette , mask ) ;
+ + hTile ;
pixelOffset + = 8 ;
// Advance one line
maskTable + = 2 ;
line + = 496 ;
}
2012-01-27 05:52:59 +00:00
}
2016-05-08 19:27:08 +00:00
std : : pair < bool , bool > CRender2D : : DrawTilemaps ( uint32_t * pixelsBottom , uint32_t * pixelsTop )
2012-01-27 05:52:59 +00:00
{
2016-05-07 21:54:03 +00:00
unsigned priority = ( m_regs [ 0x20 / 4 ] > > 8 ) & 0xF ;
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
// Render bottom layers
2016-05-08 19:27:08 +00:00
bool noBottomSurface = true ;
static const int bottomOrder [ 4 ] = { 3 , 2 , 1 , 0 } ;
for ( int i = 0 ; i < 4 ; i + + )
2016-05-07 21:54:03 +00:00
{
2016-05-08 19:27:08 +00:00
int layerNum = bottomOrder [ i ] ;
2016-05-07 21:54:03 +00:00
bool is4Bit = ( m_regs [ 0x20 / 4 ] & ( 1 < < ( 12 + layerNum ) ) ) ! = 0 ;
bool enabled = ( m_regs [ 0x60 / 4 + layerNum ] & 0x80000000 ) ! = 0 ;
bool selected = ( priority & ( 1 < < layerNum ) ) = = 0 ;
if ( enabled & & selected )
{
2016-05-08 19:27:08 +00:00
if ( noBottomSurface )
2016-05-07 21:54:03 +00:00
{
if ( is4Bit )
DrawLayer < 4 , false > ( pixelsBottom , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
else
DrawLayer < 8 , false > ( pixelsBottom , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
}
else
{
if ( is4Bit )
DrawLayer < 4 , true > ( pixelsBottom , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
else
DrawLayer < 8 , true > ( pixelsBottom , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
}
2016-05-08 19:27:08 +00:00
noBottomSurface = false ;
2016-05-07 21:54:03 +00:00
}
}
// Render top layers
2016-05-08 19:27:08 +00:00
// NOTE: layer ordering is different according to MAME (which has 3, 2, 0, 1
// for top layer). Until I see evidence that this is correct and not a typo,
// I will assume consistent layer ordering.
bool noTopSurface = true ;
static const int topOrder [ 4 ] = { 3 , 2 , 1 , 0 } ;
for ( int i = 0 ; i < 4 ; i + + )
2016-05-07 21:54:03 +00:00
{
2016-05-08 19:27:08 +00:00
int layerNum = topOrder [ i ] ;
2016-05-07 21:54:03 +00:00
bool is4Bit = ( m_regs [ 0x20 / 4 ] & ( 1 < < ( 12 + layerNum ) ) ) ! = 0 ;
bool enabled = ( m_regs [ 0x60 / 4 + layerNum ] & 0x80000000 ) ! = 0 ;
bool selected = ( priority & ( 1 < < layerNum ) ) ! = 0 ;
if ( enabled & & selected )
{
2016-05-08 19:27:08 +00:00
if ( noTopSurface )
2016-05-07 21:54:03 +00:00
{
if ( is4Bit )
DrawLayer < 4 , false > ( pixelsTop , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
else
DrawLayer < 8 , false > ( pixelsTop , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
}
else
{
if ( is4Bit )
DrawLayer < 4 , true > ( pixelsTop , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
else
DrawLayer < 8 , true > ( pixelsTop , layerNum , m_vram , m_regs , m_palette [ layerNum / 2 ] ) ;
}
2016-05-08 19:27:08 +00:00
noTopSurface = false ;
2016-05-07 21:54:03 +00:00
}
}
2011-04-24 01:14:00 +00:00
2016-05-08 19:27:08 +00:00
// Indicate whether top and bottom surfaces have to be rendered
return std : : pair < bool , bool > ( ! noTopSurface , ! noBottomSurface ) ;
2011-04-24 01:14:00 +00:00
}
2016-05-07 21:54:03 +00:00
2011-04-24 01:14:00 +00:00
/******************************************************************************
Frame Display Functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2012-01-27 05:52:59 +00:00
// Draws a surface to the screen (0 is top and 1 is bottom)
2016-12-04 22:37:22 +00:00
void CRender2D : : DisplaySurface ( int surface )
2020-12-20 18:17:34 +00:00
{
2022-11-07 21:33:01 +00:00
// Shader program
m_shader . EnableShader ( ) ;
glBindVertexArray ( m_vao ) ;
2016-05-07 21:54:03 +00:00
// Draw the surface
glActiveTexture ( GL_TEXTURE0 ) ; // texture unit 0
glBindTexture ( GL_TEXTURE_2D , m_texID [ surface ] ) ;
2022-11-07 21:33:01 +00:00
glDrawArrays ( GL_TRIANGLE_STRIP , 0 , 4 ) ;
glBindVertexArray ( 0 ) ;
m_shader . DisableShader ( ) ;
2011-04-24 01:14:00 +00:00
}
// Set up viewport and OpenGL state for 2D rendering (sets up blending function but disables blending)
2018-12-08 19:55:26 +00:00
void CRender2D : : Setup2D ( bool isBottom )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
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 ) ;
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
// Clear everything if requested or just overscan areas for wide screen mode
2018-12-08 19:55:26 +00:00
if ( isBottom )
2016-05-07 21:54:03 +00:00
{
glClearColor ( 0.0 , 0.0 , 0.0 , 0.0 ) ;
glViewport ( 0 , 0 , m_totalXPixels , m_totalYPixels ) ;
2020-12-20 18:17:34 +00:00
glDisable ( GL_SCISSOR_TEST ) ; // scissor is enabled to fix the 2d/3d miss match problem
2018-12-08 19:55:26 +00:00
glClear ( GL_COLOR_BUFFER_BIT ) ; // we want to clear outside the scissored areas so must disable it
2020-12-20 18:17:34 +00:00
glEnable ( GL_SCISSOR_TEST ) ;
2016-05-07 21:54:03 +00:00
}
// Set up the viewport and orthogonal projection
2020-12-20 18:17:34 +00:00
bool stretchBottom = m_config [ " WideBackground " ] . ValueAs < bool > ( ) & & isBottom ;
if ( ! stretchBottom )
{
glViewport ( m_xOffset - m_correction , m_yOffset + m_correction , m_xPixels , m_yPixels ) ; //Preserve aspect ratio of tile layer by constraining and centering viewport
}
2011-04-24 01:14:00 +00:00
}
void CRender2D : : BeginFrame ( void )
2016-05-08 19:27:08 +00:00
{
}
void CRender2D : : PreRenderFrame ( void )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
// Update all layers
2016-05-08 19:27:08 +00:00
m_surfaces_present = DrawTilemaps ( m_bottomSurface , m_topSurface ) ;
2016-05-07 21:54:03 +00:00
glActiveTexture ( GL_TEXTURE0 ) ; // texture unit 0
2016-05-08 19:27:08 +00:00
if ( m_surfaces_present . first )
{
glBindTexture ( GL_TEXTURE_2D , m_texID [ 0 ] ) ;
glTexSubImage2D ( GL_TEXTURE_2D , 0 , 0 , 0 , 496 , 384 , GL_RGBA , GL_UNSIGNED_BYTE , m_topSurface ) ;
}
if ( m_surfaces_present . second )
{
glBindTexture ( GL_TEXTURE_2D , m_texID [ 1 ] ) ;
glTexSubImage2D ( GL_TEXTURE_2D , 0 , 0 , 0 , 496 , 384 , GL_RGBA , GL_UNSIGNED_BYTE , m_bottomSurface ) ;
}
}
void CRender2D : : RenderFrameBottom ( void )
{
// Display bottom surface if anything was drawn there, else clear everything
2018-12-08 19:55:26 +00:00
Setup2D ( true ) ;
2016-05-08 19:27:08 +00:00
if ( m_surfaces_present . second )
2016-12-04 22:37:22 +00:00
DisplaySurface ( 1 ) ;
2011-04-24 01:14:00 +00:00
}
2016-05-08 19:27:08 +00:00
void CRender2D : : RenderFrameTop ( void )
{
// Display top surface only if it exists
if ( m_surfaces_present . first )
{
2018-12-08 19:55:26 +00:00
Setup2D ( false ) ;
2016-05-08 19:27:08 +00:00
glEnable ( GL_BLEND ) ;
2016-12-04 22:37:22 +00:00
DisplaySurface ( 0 ) ;
2016-05-08 19:27:08 +00:00
}
}
2011-04-24 01:14:00 +00:00
void CRender2D : : EndFrame ( void )
2016-03-21 04:10:14 +00:00
{
2011-04-24 01:14:00 +00:00
}
/******************************************************************************
Emulation Callbacks
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2016-03-22 12:30:23 +00:00
2012-02-23 00:12:46 +00:00
// Deprecated
2016-05-07 21:54:03 +00:00
void CRender2D : : WriteVRAM ( unsigned addr , uint32_t data )
2011-07-04 20:53:37 +00:00
{
}
2011-04-24 01:14:00 +00:00
/******************************************************************************
Configuration , Initialization , and Shutdown
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2016-05-07 21:54:03 +00:00
void CRender2D : : AttachRegisters ( const uint32_t * regPtr )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
m_regs = regPtr ;
DebugLog ( " Render2D attached registers \n " ) ;
2011-04-24 01:14:00 +00:00
}
2016-05-07 21:54:03 +00:00
void CRender2D : : AttachPalette ( const uint32_t * palPtr [ 2 ] )
2012-01-16 23:21:14 +00:00
{
2016-05-07 21:54:03 +00:00
m_palette [ 0 ] = palPtr [ 0 ] ;
m_palette [ 1 ] = palPtr [ 1 ] ;
DebugLog ( " Render2D attached palette \n " ) ;
2012-01-16 23:21:14 +00:00
}
2016-05-07 21:54:03 +00:00
void CRender2D : : AttachVRAM ( const uint8_t * vramPtr )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
m_vram = ( uint32_t * ) vramPtr ;
DebugLog ( " Render2D attached VRAM \n " ) ;
2011-04-24 01:14:00 +00:00
}
2012-01-27 05:52:59 +00:00
// Memory pool and offsets within it
2016-05-07 21:54:03 +00:00
# define MEMORY_POOL_SIZE (2*512*384*4)
# define OFFSET_TOP_SURFACE 0 // 512*384*4 bytes
# define OFFSET_BOTTOM_SURFACE (512*384*4) // 512*384*4
2011-04-24 01:14:00 +00:00
2012-02-10 19:53:51 +00:00
bool CRender2D : : Init ( unsigned xOffset , unsigned yOffset , unsigned xRes , unsigned yRes , unsigned totalXRes , unsigned totalYRes )
2011-04-24 01:14:00 +00:00
{
2016-05-07 21:54:03 +00:00
// Allocate memory for layer surfaces
m_memoryPool = new ( std : : nothrow ) uint8_t [ MEMORY_POOL_SIZE ] ;
if ( NULL = = m_memoryPool )
return ErrorLog ( " Insufficient memory for tilemap surfaces (need %1.1f MB) . " , float(MEMORY_POOL_SIZE) / 0x100000) ;
memset ( m_memoryPool , 0 , MEMORY_POOL_SIZE ) ; // clear textures
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
// Set up pointers to memory regions
m_topSurface = ( uint32_t * ) & m_memoryPool [ OFFSET_TOP_SURFACE ] ;
m_bottomSurface = ( uint32_t * ) & m_memoryPool [ OFFSET_BOTTOM_SURFACE ] ;
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
// Resolution
m_xPixels = xRes ;
m_yPixels = yRes ;
m_xOffset = xOffset ;
m_yOffset = yOffset ;
m_totalXPixels = totalXRes ;
m_totalYPixels = totalYRes ;
2017-03-19 01:33:45 +00:00
m_correction = ( UINT32 ) ( ( ( yRes / 384.f ) * 2 ) + 0.5f ) ; // for some reason the 2d layer is 2 pixels off the 3D
2016-05-07 21:54:03 +00:00
2022-11-07 21:33:01 +00:00
DebugLog ( " Render2D initialized (allocated %1.1f MB) \n " , float ( MEMORY_POOL_SIZE ) / 0x100000 ) ;
return OKAY ;
}
CRender2D : : CRender2D ( const Util : : Config : : Node & config )
: m_config ( config ) ,
m_vao ( 0 )
{
DebugLog ( " Built Render2D \n " ) ;
m_shader . LoadShaders ( s_vertexShaderSource , s_fragmentShaderSource ) ;
m_shader . GetUniformLocationMap ( " tex1 " ) ;
m_shader . EnableShader ( ) ;
// update uniform memory
glUniform1i ( m_shader . uniformLocMap [ " tex1 " ] , 0 ) ; // bind to texture unit zero
m_shader . DisableShader ( ) ;
2016-05-07 21:54:03 +00:00
// Create textures
2016-12-04 22:37:22 +00:00
glActiveTexture ( GL_TEXTURE0 ) ; // texture unit 0
2016-05-07 21:54:03 +00:00
glGenTextures ( 2 , m_texID ) ;
2016-12-04 22:37:22 +00:00
2016-05-07 21:54:03 +00:00
for ( int i = 0 ; i < 2 ; i + + )
{
2022-11-07 21:33:01 +00:00
glBindTexture ( GL_TEXTURE_2D , m_texID [ i ] ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE ) ;
glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE ) ;
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 , 496 , 384 , 0 , GL_RGBA , GL_UNSIGNED_BYTE , NULL ) ;
2016-05-07 21:54:03 +00:00
}
2022-11-07 21:33:01 +00:00
glGenVertexArrays ( 1 , & m_vao ) ;
glBindVertexArray ( m_vao ) ;
// no states needed since we do it in the shader
glBindVertexArray ( 0 ) ;
2011-04-24 01:14:00 +00:00
}
CRender2D : : ~ CRender2D ( void )
2016-03-22 12:30:23 +00:00
{
2022-11-07 21:33:01 +00:00
m_shader . UnloadShaders ( ) ;
2016-05-07 21:54:03 +00:00
glDeleteTextures ( 2 , m_texID ) ;
2020-12-20 18:17:34 +00:00
2022-11-07 21:33:01 +00:00
if ( m_vao ) {
glDeleteVertexArrays ( 1 , & m_vao ) ;
m_vao = 0 ;
}
2016-05-07 21:54:03 +00:00
if ( m_memoryPool )
{
delete [ ] m_memoryPool ;
m_memoryPool = 0 ;
}
2020-12-20 18:17:34 +00:00
2016-05-07 21:54:03 +00:00
m_vram = 0 ;
m_topSurface = 0 ;
m_bottomSurface = 0 ;
DebugLog ( " Destroyed Render2D \n " ) ;
2011-04-24 01:14:00 +00:00
}