2021-11-12 20:03:04 +01:00
|
|
|
#include "Renderer.hpp"
|
|
|
|
#include "Error.hpp"
|
|
|
|
#include "Utils.hpp"
|
|
|
|
|
|
|
|
#include <glrage_gl/Utils.hpp>
|
|
|
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
#include <glm/mat4x4.hpp>
|
|
|
|
|
|
|
|
namespace glrage {
|
|
|
|
namespace cif {
|
|
|
|
|
|
|
|
using std::placeholders::_1;
|
|
|
|
|
|
|
|
Renderer::Renderer()
|
|
|
|
{
|
2021-11-14 23:59:19 +01:00
|
|
|
// The vertex stream passes primitives to the delayer to test if they have
|
|
|
|
// translucency and in that case delay them. When the delayer needs to
|
|
|
|
// display the delayed primitives it calls back to the vertex stream to do
|
|
|
|
// so.
|
|
|
|
m_vertexStream.setDelayer(
|
|
|
|
[this](C3D_VTCF* verts) { return m_transDelay.delayTriangle(verts); });
|
2021-11-12 20:03:04 +01:00
|
|
|
|
|
|
|
// bind sampler
|
|
|
|
m_sampler.bind(0);
|
|
|
|
|
|
|
|
// improve texture filtering quality
|
|
|
|
float filterAniso = m_config.getFloat("ati3dcif.filter_anisotropy", 16.0f);
|
|
|
|
if (filterAniso > 0) {
|
|
|
|
m_sampler.parameterf(GL_TEXTURE_MAX_ANISOTROPY_EXT, filterAniso);
|
|
|
|
}
|
|
|
|
|
|
|
|
// compile and link shaders and configure program
|
2021-11-14 23:42:18 +01:00
|
|
|
std::string basePath = m_context.getBasePath();
|
2021-11-12 20:03:04 +01:00
|
|
|
m_program.attach(gl::Shader(GL_VERTEX_SHADER)
|
2021-11-14 23:42:18 +01:00
|
|
|
.fromFile(basePath + "\\shaders\\ati3dcif.vsh"));
|
2021-11-12 20:03:04 +01:00
|
|
|
m_program.attach(gl::Shader(GL_FRAGMENT_SHADER)
|
2021-11-14 23:42:18 +01:00
|
|
|
.fromFile(basePath + "\\shaders\\ati3dcif.fsh"));
|
2021-11-12 20:03:04 +01:00
|
|
|
m_program.link();
|
|
|
|
m_program.fragmentData("fragColor");
|
|
|
|
m_program.bind();
|
|
|
|
|
|
|
|
// negate Z axis so the model is rendered behind the viewport, which is
|
2021-11-14 23:44:58 +01:00
|
|
|
// better than having a negative zNear in the ortho matrix, which seems
|
|
|
|
// to mess up depth testing
|
2021-11-12 20:03:04 +01:00
|
|
|
auto modelView = glm::scale(glm::mat4(), glm::vec3(1, 1, -1));
|
|
|
|
m_program.uniformMatrix4fv(
|
|
|
|
"matModelView", 1, GL_FALSE, glm::value_ptr(modelView));
|
|
|
|
|
|
|
|
// cache frequently used config values
|
|
|
|
m_wireframe = m_config.getBool("ati3dcif.wireframe", false);
|
|
|
|
|
|
|
|
gl::Utils::checkError(__FUNCTION__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::renderBegin(C3D_HRC hRC)
|
|
|
|
{
|
|
|
|
glEnable(GL_BLEND);
|
|
|
|
|
|
|
|
// set wireframe mode if set
|
|
|
|
if (m_wireframe) {
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// bind objects
|
|
|
|
m_program.bind();
|
|
|
|
m_vertexStream.bind();
|
|
|
|
m_sampler.bind(0);
|
|
|
|
|
|
|
|
// restore texture binding
|
|
|
|
tmapRestore();
|
|
|
|
|
|
|
|
// CIF always uses an orthographic view, the application deals with the
|
|
|
|
// perspective when required
|
|
|
|
auto width = static_cast<float>(m_context.getDisplayWidth());
|
|
|
|
auto height = static_cast<float>(m_context.getDisplayHeight());
|
|
|
|
auto projection = glm::ortho<float>(0, width, height, 0, -1e6, 1e6);
|
|
|
|
m_program.uniformMatrix4fv(
|
|
|
|
"matProjection", 1, GL_FALSE, glm::value_ptr(projection));
|
|
|
|
|
|
|
|
gl::Utils::checkError(__FUNCTION__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::renderEnd()
|
|
|
|
{
|
|
|
|
// make sure everything has been rendered
|
|
|
|
m_vertexStream.renderPending();
|
|
|
|
// including the delayed translucent primitives
|
|
|
|
m_program.uniform1i("keyOnAlpha", true);
|
2021-11-14 23:59:19 +01:00
|
|
|
m_transDelay.render([this](std::vector<C3D_VTCF> verts) {
|
|
|
|
m_vertexStream.renderPrims(verts);
|
|
|
|
});
|
2021-11-12 20:03:04 +01:00
|
|
|
|
|
|
|
// restore polygon mode
|
|
|
|
if (m_wireframe) {
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
|
|
}
|
|
|
|
|
|
|
|
gl::Utils::checkError(__FUNCTION__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::textureReg(C3D_PTMAP ptmapToReg, C3D_PHTX phtmap)
|
|
|
|
{
|
|
|
|
// LOG_TRACE("fmt=%d, xlg2=%d, ylg2=%d, mip=%d",
|
|
|
|
// ptmapToReg->eTexFormat, ptmapToReg->u32MaxMapXSizeLg2,
|
|
|
|
// ptmapToReg->u32MaxMapYSizeLg2, ptmapToReg->bMipMap);
|
|
|
|
|
|
|
|
auto texture = std::make_shared<Texture>();
|
|
|
|
texture->bind();
|
|
|
|
texture->load(ptmapToReg, m_palettes[ptmapToReg->htxpalTexPalette]);
|
|
|
|
|
|
|
|
// use id as texture handle
|
|
|
|
*phtmap = reinterpret_cast<C3D_HTX>(texture->id());
|
|
|
|
|
|
|
|
// store in texture map
|
|
|
|
m_textures[*phtmap] = texture;
|
|
|
|
|
|
|
|
// restore previously bound texture
|
|
|
|
tmapRestore();
|
|
|
|
|
|
|
|
gl::Utils::checkError(__FUNCTION__);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::textureUnreg(C3D_HTX htxToUnreg)
|
|
|
|
{
|
|
|
|
// LOG_TRACE("id=%d", id);
|
|
|
|
|
|
|
|
auto it = m_textures.find(htxToUnreg);
|
|
|
|
if (it == m_textures.end()) {
|
|
|
|
throw Error("Invalid texture handle", C3D_EC_BADPARAM);
|
|
|
|
}
|
|
|
|
|
|
|
|
// unbind texture if currently bound
|
2021-11-14 23:44:58 +01:00
|
|
|
if (htxToUnreg == m_tmapSelect) {
|
|
|
|
tmapSelect(0);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Texture> texture = it->second;
|
|
|
|
m_textures.erase(htxToUnreg);
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:59:19 +01:00
|
|
|
void Renderer::texturePaletteCreate(C3D_ECI_TMAP_TYPE epalette,
|
|
|
|
void* pPalette,
|
|
|
|
C3D_PHTXPAL phtpalCreated)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
|
|
|
if (epalette != C3D_ECI_TMAP_8BIT) {
|
|
|
|
throw Error("Unsupported palette type: " +
|
|
|
|
std::string(C3D_ECI_TMAP_TYPE_NAMES[epalette]),
|
|
|
|
C3D_EC_NOTIMPYET);
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy palette entries to vector
|
|
|
|
auto palettePtr = static_cast<C3D_PPALETTENTRY>(pPalette);
|
|
|
|
std::vector<C3D_PALETTENTRY> palette(palettePtr, palettePtr + 256);
|
|
|
|
|
|
|
|
// create new palette handle
|
|
|
|
auto handle = reinterpret_cast<C3D_HTXPAL>(m_paletteID++);
|
|
|
|
|
|
|
|
// store palette
|
|
|
|
m_palettes[handle] = palette;
|
|
|
|
|
|
|
|
*phtpalCreated = handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::texturePaletteDestroy(C3D_HTXPAL htxpalToDestroy)
|
|
|
|
{
|
|
|
|
m_palettes.erase(htxpalToDestroy);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::renderPrimStrip(C3D_VSTRIP vStrip, C3D_UINT32 u32NumVert)
|
|
|
|
{
|
|
|
|
m_context.setRendered();
|
|
|
|
m_vertexStream.addPrimStrip(vStrip, u32NumVert);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::renderPrimList(C3D_VLIST vList, C3D_UINT32 u32NumVert)
|
|
|
|
{
|
|
|
|
m_context.setRendered();
|
|
|
|
m_vertexStream.addPrimList(vList, u32NumVert);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::setState(C3D_ERSID eRStateID, C3D_PRSDATA pRStateData)
|
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
switch (eRStateID) {
|
2021-11-14 23:59:19 +01:00
|
|
|
case C3D_ERS_VERTEX_TYPE:
|
|
|
|
vertexType(*reinterpret_cast<C3D_EVERTEX*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_PRIM_TYPE:
|
|
|
|
primType(*reinterpret_cast<C3D_EPRIM*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_SOLID_CLR:
|
|
|
|
solidColor(*reinterpret_cast<C3D_COLOR*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_SHADE_MODE:
|
|
|
|
shadeMode(*reinterpret_cast<C3D_ESHADE*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_TMAP_EN:
|
|
|
|
tmapEnable(*reinterpret_cast<C3D_BOOL*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_TMAP_SELECT:
|
|
|
|
tmapSelect(*reinterpret_cast<C3D_HTX*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_TMAP_LIGHT:
|
|
|
|
tmapLight(*reinterpret_cast<C3D_ETLIGHT*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_TMAP_FILTER:
|
|
|
|
tmapFilter(*reinterpret_cast<C3D_ETEXFILTER*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_TMAP_TEXOP:
|
|
|
|
tmapTexOp(*reinterpret_cast<C3D_ETEXOP*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_ALPHA_SRC:
|
|
|
|
alphaSrc(*reinterpret_cast<C3D_EASRC*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_ALPHA_DST:
|
|
|
|
alphaDst(*reinterpret_cast<C3D_EADST*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_Z_CMP_FNC:
|
|
|
|
zCmpFunc(*reinterpret_cast<C3D_EZCMP*>(pRStateData));
|
|
|
|
break;
|
|
|
|
case C3D_ERS_Z_MODE:
|
|
|
|
zMode(*reinterpret_cast<C3D_EZMODE*>(pRStateData));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw Error("Unsupported state: " + std::to_string(eRStateID),
|
|
|
|
C3D_EC_NOTIMPYET);
|
2021-11-14 23:44:58 +01:00
|
|
|
}
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::vertexType(C3D_EVERTEX value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
|
|
|
m_vertexStream.renderPending();
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.vertexType(value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::primType(C3D_EPRIM value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_vertexStream.primType(value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::solidColor(C3D_COLOR value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
C3D_COLOR color = value;
|
2021-11-14 23:59:19 +01:00
|
|
|
m_program.uniform4f("solidColor",
|
|
|
|
color.r / 255.0f,
|
|
|
|
color.g / 255.0f,
|
|
|
|
color.b / 255.0f,
|
|
|
|
color.a / 255.0f);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::shadeMode(C3D_ESHADE value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_program.uniform1i("shadeMode", value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::tmapEnable(C3D_BOOL value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
C3D_BOOL enable = value;
|
2021-11-12 20:03:04 +01:00
|
|
|
m_program.uniform1i("tmapEn", enable);
|
|
|
|
m_transDelay.setTexturingEnabled(enable != C3D_FALSE);
|
|
|
|
if (enable) {
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
} else {
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::tmapSelect(C3D_HTX value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_tmapSelect = value;
|
|
|
|
tmapSelectImpl(value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::tmapSelectImpl(C3D_HTX handle)
|
|
|
|
{
|
|
|
|
// unselect texture if handle is zero
|
|
|
|
if (handle == 0) {
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if handle is correct
|
|
|
|
auto it = m_textures.find(handle);
|
|
|
|
if (it == m_textures.end()) {
|
|
|
|
throw Error("Invalid texture handle", C3D_EC_BADPARAM);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get texture object and bind it
|
|
|
|
auto texture = it->second;
|
|
|
|
texture->bind();
|
|
|
|
// Tell the transparent primitive delayer what texture is currently in use
|
|
|
|
m_transDelay.setTexture(texture);
|
|
|
|
|
|
|
|
// send chroma key color to shader
|
|
|
|
auto ck = texture->chromaKey();
|
|
|
|
m_program.uniform3f(
|
|
|
|
"chromaKey", ck.r / 255.0f, ck.g / 255.0f, ck.b / 255.0f);
|
|
|
|
m_program.uniform1i("keyOnAlpha", texture->keyOnAlpha());
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:59:19 +01:00
|
|
|
void Renderer::tmapRestore()
|
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
tmapSelectImpl(m_tmapSelect);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::tmapLight(C3D_ETLIGHT value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_program.uniform1i("tmapLight", value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::tmapFilter(C3D_ETEXFILTER value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
auto filter = value;
|
2021-11-12 20:03:04 +01:00
|
|
|
m_sampler.parameteri(
|
|
|
|
GL_TEXTURE_MAG_FILTER, GLCIF_TEXTURE_MAG_FILTER[filter]);
|
|
|
|
m_sampler.parameteri(
|
|
|
|
GL_TEXTURE_MIN_FILTER, GLCIF_TEXTURE_MIN_FILTER[filter]);
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::tmapTexOp(C3D_ETEXOP value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_program.uniform1i("texOp", value);
|
2021-11-12 20:03:04 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::alphaSrc(C3D_EASRC value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_easrc = value;
|
|
|
|
C3D_EASRC alphaSrc = value;
|
|
|
|
C3D_EADST alphaDst = m_eadst;
|
2021-11-12 20:03:04 +01:00
|
|
|
glBlendFunc(GLCIF_BLEND_FUNC[alphaSrc], GLCIF_BLEND_FUNC[alphaDst]);
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::alphaDst(C3D_EADST value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
m_eadst = value;
|
2021-11-14 23:59:19 +01:00
|
|
|
C3D_EASRC alphaSrc = m_easrc;
|
2021-11-14 23:44:58 +01:00
|
|
|
C3D_EADST alphaDst = value;
|
2021-11-12 20:03:04 +01:00
|
|
|
glBlendFunc(GLCIF_BLEND_FUNC[alphaSrc], GLCIF_BLEND_FUNC[alphaDst]);
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::zCmpFunc(C3D_EZCMP value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
C3D_EZCMP func = value;
|
2021-11-12 20:03:04 +01:00
|
|
|
if (func < C3D_EZCMP_MAX) {
|
|
|
|
glDepthFunc(GLCIF_DEPTH_FUNC[func]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 23:44:58 +01:00
|
|
|
void Renderer::zMode(C3D_EZMODE value)
|
2021-11-12 20:03:04 +01:00
|
|
|
{
|
2021-11-14 23:44:58 +01:00
|
|
|
m_vertexStream.renderPending();
|
|
|
|
auto mode = value;
|
2021-11-12 20:03:04 +01:00
|
|
|
glDepthMask(GLCIF_DEPTH_MASK[mode]);
|
|
|
|
|
|
|
|
if (mode > C3D_EZMODE_TESTON) {
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
} else {
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace cif
|
2021-11-14 23:42:18 +01:00
|
|
|
} // namespace glrage
|