flycast/core/rend/transform_matrix.h

343 lines
9.6 KiB
C++

/*
Created on: Oct 22, 2019
Copyright 2019 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "TexCache.h"
#include "hw/pvr/ta_ctx.h"
#include "cfg/option.h"
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
inline static void getTAViewport(const rend_context& rendCtx, int& width, int& height)
{
width = (rendCtx.ta_GLOB_TILE_CLIP.tile_x_num + 1) * 32;
height = (rendCtx.ta_GLOB_TILE_CLIP.tile_y_num + 1) * 32;
}
inline static void getPvrFramebufferSize(const rend_context& rendCtx, int& width, int& height)
{
getTAViewport(rendCtx, width, height);
if (!config::EmulateFramebuffer)
{
int maxHeight = FB_R_CTRL.vclk_div == 0 && SPG_CONTROL.interlace == 0 ? 240 : 480;
if (rendCtx.scaler_ctl.vscalefactor != 0
&& (rendCtx.scaler_ctl.vscalefactor > 1025 || rendCtx.scaler_ctl.vscalefactor < 1024)
&& SPG_CONTROL.interlace == 0)
maxHeight /= 1024.f / rendCtx.scaler_ctl.vscalefactor;
if (FB_R_CTRL.fb_line_double)
maxHeight /= 2;
height = std::min(maxHeight, height);
// TODO Use FB_R_SIZE too?
}
}
// Dreamcast:
// +Y is down
// OpenGL:
// +Y is up in clip, NDC and framebuffer coordinates
// Vulkan:
// +Y is down in clip, NDC and framebuffer coordinates
// DirectX9:
// +Y is up in clip and NDC coordinates, but down in framebuffer coordinates
// Y must also be flipped for render-to-texture so that the top of the texture comes first
enum CoordSystem { COORD_OPENGL, COORD_VULKAN, COORD_DIRECTX };
template<CoordSystem System>
class TransformMatrix
{
public:
TransformMatrix() = default;
TransformMatrix(const rend_context& renderingContext, int width, int height)
{
CalcMatrices(&renderingContext, width, height);
}
bool IsClipped() const
{
int width, height;
getTAViewport(*renderingContext, width, height);
float sx, sy;
GetScissorScaling(sx, sy);
return renderingContext->fb_X_CLIP.min != 0
|| lroundf((renderingContext->fb_X_CLIP.max + 1) / sx) != width;
}
const glm::mat4& GetNormalMatrix() const {
return normalMatrix;
}
const glm::mat4& GetScissorMatrix() const {
return scissorMatrix;
}
const glm::mat4& GetViewportMatrix() const {
return viewportMatrix;
}
// Return the width of the black bars when the screen is wider than 4:3. Returns a negative number when the screen is taller than 4:3,
// whose inverse is the height of the top and bottom bars.
float GetSidebarWidth() const {
return sidebarWidth;
}
glm::vec2 GetDreamcastViewport() const {
return dcViewport;
}
void CalcMatrices(const rend_context *renderingContext, int width = 0, int height = 0)
{
constexpr int flipY = System == COORD_DIRECTX ? -1 : 1;
renderViewport = { width == 0 ? settings.display.width : width, height == 0 ? settings.display.height : height };
this->renderingContext = renderingContext;
if (renderingContext->isRTT)
{
dcViewport.x = (float)(renderingContext->fb_X_CLIP.max - renderingContext->fb_X_CLIP.min + 1);
if (renderingContext->scaler_ctl.hscale)
dcViewport.x *= 2;
dcViewport.y = (float)(renderingContext->fb_Y_CLIP.max - renderingContext->fb_Y_CLIP.min + 1);
normalMatrix = glm::translate(glm::vec3(-1, -flipY, 0))
* glm::scale(glm::vec3(2.0f / dcViewport.x, 2.0f / dcViewport.y * flipY, 1.f));
scissorMatrix = normalMatrix;
sidebarWidth = 0;
}
else
{
int w, h;
getPvrFramebufferSize(*renderingContext, w, h);
dcViewport.x = w;
dcViewport.y = h;
float scissoring_scale_x, scissoring_scale_y;
GetScissorScaling(scissoring_scale_x, scissoring_scale_y);
if (config::Widescreen && !config::Rotate90 && !config::EmulateFramebuffer)
{
sidebarWidth = (1 - dcViewport.x / dcViewport.y * renderViewport.y / renderViewport.x) / 2;
if (config::SuperWidescreen)
dcViewport.x *= (float)settings.display.width / settings.display.height / 4.f * 3.f;
else
dcViewport.x *= 4.f / 3.f;
}
else
sidebarWidth = 0;
float x_coef = 2.0f / dcViewport.x;
float y_coef = 2.0f / dcViewport.y * flipY;
glm::mat4 trans = glm::translate(glm::vec3(-1 + 2 * sidebarWidth, -flipY, 0));
normalMatrix = trans
* glm::scale(glm::vec3(x_coef, y_coef, 1.f));
scissorMatrix = trans
* glm::scale(glm::vec3(x_coef * scissoring_scale_x, y_coef * scissoring_scale_y, 1.f));
}
normalMatrix = glm::scale(glm::vec3(1, 1, 1 / config::ExtraDepthScale))
* normalMatrix;
glm::mat4 vp_trans = glm::translate(glm::vec3(1, flipY, 0));
if (renderingContext->isRTT)
{
vp_trans = glm::scale(glm::vec3(dcViewport.x / 2, dcViewport.y / 2 * flipY, 1.f))
* vp_trans;
}
else
{
vp_trans = glm::scale(glm::vec3(renderViewport.x / 2, renderViewport.y / 2 * flipY, 1.f))
* vp_trans;
}
viewportMatrix = vp_trans * normalMatrix;
scissorMatrix = vp_trans * scissorMatrix;
}
private:
void GetScissorScaling(float& scale_x, float& scale_y) const
{
scale_x = 1.f;
scale_y = 1.f;
if (!renderingContext->isRTT && !config::EmulateFramebuffer)
{
if (renderingContext->scaler_ctl.vscalefactor > 0x400)
scale_y *= std::round(renderingContext->scaler_ctl.vscalefactor / 1024.f);
if (renderingContext->scaler_ctl.hscale)
scale_x *= 2.f;
}
else if (config::EmulateFramebuffer)
{
if (renderingContext->scaler_ctl.hscale)
scale_x *= 2.f;
// vscalefactor is applied after scissoring if > 1
if (renderingContext->scaler_ctl.vscalefactor > 0x401 || renderingContext->scaler_ctl.vscalefactor < 0x400)
{
float vscalefactor = 1024.f / renderingContext->scaler_ctl.vscalefactor;
if (vscalefactor < 1)
scale_y /= vscalefactor;
}
}
}
const rend_context *renderingContext = nullptr;
glm::mat4 normalMatrix;
glm::mat4 scissorMatrix;
glm::mat4 viewportMatrix;
glm::vec2 dcViewport;
glm::vec2 renderViewport;
float sidebarWidth = 0;
};
inline static void getScaledFramebufferSize(const rend_context& rendCtx, int& width, int& height)
{
getPvrFramebufferSize(rendCtx, width, height);
if (!config::EmulateFramebuffer)
{
float upscaling = config::RenderResolution / 480.f;
float w = width * upscaling;
float h = height * upscaling;
if (config::Widescreen && !config::Rotate90)
{
if (config::SuperWidescreen)
w *= (float)settings.display.width / settings.display.height / 4.f * 3.f;
else
w *= 4.f / 3.f;
}
if (!config::Rotate90)
w = std::round(w / 2.f) * 2.f;
h = std::round(h);
width = w;
height = h;
}
}
inline static float getOutputFramebufferAspectRatio()
{
float aspectRatio;
if (config::Rotate90)
{
aspectRatio = 3.f / 4.f;
}
else
{
if (config::Widescreen && !config::EmulateFramebuffer)
{
if (config::SuperWidescreen)
aspectRatio = (float)settings.display.width / settings.display.height;
else
aspectRatio = 16.f / 9.f;
}
else
{
aspectRatio = 4.f / 3.f;
}
}
return aspectRatio * config::ScreenStretching / 100.f;
}
inline static void getDCFramebufferReadSize(const FramebufferInfo& info, int& width, int& height)
{
width = (info.fb_r_size.fb_x_size + 1) * 2; // in 16-bit words
height = info.fb_r_size.fb_y_size + 1;
int modulus = (info.fb_r_size.fb_modulus - 1) * 2;
int bpp;
switch (info.fb_r_ctrl.fb_depth)
{
case fbde_0555:
case fbde_565:
bpp = 2;
break;
case fbde_888:
bpp = 3;
width = (width * 2) / 3; // in pixels
modulus = (modulus * 2) / 3; // in pixels
break;
case fbde_C888:
bpp = 4;
width /= 2; // in pixels
modulus /= 2; // in pixels
break;
default:
bpp = 2;
break;
}
if (info.spg_control.interlace && width == modulus && info.fb_r_sof2 == info.fb_r_sof1 + width * bpp)
// Typical case alternating even and odd lines -> take the whole buffer at once
height *= 2;
}
inline static float getDCFramebufferAspectRatio()
{
float aspectRatio = config::Rotate90 ? 3.f / 4.f : 4.f / 3.f;
return aspectRatio * config::ScreenStretching / 100.f;
}
inline static void getVideoShift(float& x, float& y)
{
const bool vga = FB_R_CTRL.vclk_div == 1;
switch (SPG_LOAD.hcount)
{
case 857: // NTSC, VGA
x = VO_STARTX.HStart - (vga ? 0xa8 : 0xa4);
break;
case 863: // PAL
x = VO_STARTX.HStart - 0xae;
break;
case 851: // Naomi
x = VO_STARTX.HStart - 0xa5; // a0 for 15 kHz
break;
default:
x = 0;
INFO_LOG(RENDERER, "unknown video mode: hcount %d", SPG_LOAD.hcount);
break;
}
switch (SPG_LOAD.vcount)
{
case 524: // NTSC, VGA
y = VO_STARTY.VStart_field1 - (vga ? 0x28 : 0x12);
break;
case 262: // NTSC 240p
y = VO_STARTY.VStart_field1 - 0x11;
break;
case 624: // PAL
y = VO_STARTY.VStart_field1 - 0x2d;
break;
case 312: // PAL 240p
y = VO_STARTY.VStart_field1 - 0x2e;
break;
case 529: // Naomi 31 kHz
y = VO_STARTY.VStart_field1 - 0x24;
break;
case 536: // Naomi 15 kHz 480i
case 268: // Naomi 15 kHz 240p
y = VO_STARTY.VStart_field1 - 0x17; // 16 for 240p
break;
default:
y = 0;
INFO_LOG(RENDERER, "unknown video mode: vcount %d", SPG_LOAD.vcount);
break;
}
if (!config::EmulateFramebuffer)
{
x *= config::RenderResolution / 480.f;
y *= config::RenderResolution / 480.f;
}
x *= config::ScreenStretching / 100.f;
}