mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-05-21 19:16:25 +12:00
resolve merge conflicts
This commit is contained in:
commit
804a0b083f
48 changed files with 7770 additions and 528 deletions
include
PICA
dynapica
gpu.hpppica_frag_config.hpppica_frag_uniforms.hppregs.hppshader.hppshader_decompiler.hppshader_gen.hppshader_gen_types.hpploader
panda_qt
renderer.hpprenderer_gl
|
@ -22,8 +22,11 @@ class ShaderJIT {
|
|||
|
||||
ShaderCache cache;
|
||||
#endif
|
||||
bool accurateMul = false;
|
||||
|
||||
public:
|
||||
void setAccurateMul(bool value) { accurateMul = value; }
|
||||
|
||||
#ifdef PANDA3DS_SHADER_JIT_SUPPORTED
|
||||
// Call this before starting to process a batch of vertices
|
||||
// This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader
|
||||
|
@ -36,11 +39,11 @@ class ShaderJIT {
|
|||
static constexpr bool isAvailable() { return true; }
|
||||
#else
|
||||
void prepare(PICAShader& shaderUnit) {
|
||||
Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Prepare on platform that does not support shader jit");
|
||||
Helpers::panic("Shader JIT: Tried to run ShaderJIT::Prepare on platform that does not support shader jit");
|
||||
}
|
||||
|
||||
void run(PICAShader& shaderUnit) {
|
||||
Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Run on platform that does not support shader jit");
|
||||
Helpers::panic("Shader JIT: Tried to run ShaderJIT::Run on platform that does not support shader jit");
|
||||
}
|
||||
|
||||
// Define dummy callback. This should never be called if the shader JIT is not supported
|
||||
|
|
|
@ -37,6 +37,8 @@ class ShaderEmitter : private oaknut::CodeBlock, public oaknut::CodeGenerator {
|
|||
// Shows whether the loaded shader has any log2 and exp2 instructions
|
||||
bool codeHasLog2 = false;
|
||||
bool codeHasExp2 = false;
|
||||
// Whether to compile this shader using accurate, safe, non-IEEE multiplication (slow) or faster but less accurate mul
|
||||
bool useSafeMUL = false;
|
||||
|
||||
oaknut::Label log2Func, exp2Func;
|
||||
oaknut::Label emitLog2Func();
|
||||
|
@ -123,7 +125,7 @@ class ShaderEmitter : private oaknut::CodeBlock, public oaknut::CodeGenerator {
|
|||
PrologueCallback prologueCb = nullptr;
|
||||
|
||||
// Initialize our emitter with "allocSize" bytes of memory allocated for the code buffer
|
||||
ShaderEmitter() : oaknut::CodeBlock(allocSize), oaknut::CodeGenerator(oaknut::CodeBlock::ptr()) {}
|
||||
ShaderEmitter(bool useSafeMUL) : oaknut::CodeBlock(allocSize), oaknut::CodeGenerator(oaknut::CodeBlock::ptr()), useSafeMUL(useSafeMUL) {}
|
||||
|
||||
// PC must be a valid entrypoint here. It doesn't have that much overhead in this case, so we use std::array<>::at() to assert it does
|
||||
InstructionCallback getInstructionCallback(u32 pc) { return getLabelPointer<InstructionCallback>(instructionLabels.at(pc)); }
|
||||
|
|
|
@ -32,6 +32,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
Label negateVector;
|
||||
// Vector value of (1.0, 1.0, 1.0, 1.0) for SLT(i)/SGE(i)
|
||||
Label onesVector;
|
||||
// Vector value of (0xFF, 0xFF, 0xFF, 0) for setting the w component to 0 in DP3
|
||||
Label dp3Vector;
|
||||
|
||||
u32 recompilerPC = 0; // PC the recompiler is currently recompiling @
|
||||
u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop)
|
||||
|
@ -43,12 +45,17 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
// Shows whether the loaded shader has any log2 and exp2 instructions
|
||||
bool codeHasLog2 = false;
|
||||
bool codeHasExp2 = false;
|
||||
// Whether to compile this shader using accurate, safe, non-IEEE multiplication (slow) or faster but less accurate mul
|
||||
bool useSafeMUL = false;
|
||||
|
||||
Xbyak::Label log2Func, exp2Func;
|
||||
Xbyak::Label emitLog2Func();
|
||||
Xbyak::Label emitExp2Func();
|
||||
Xbyak::util::Cpu cpuCaps;
|
||||
|
||||
// Emit a PICA200-compliant multiplication that handles "0 * inf = 0"
|
||||
void emitSafeMUL(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch);
|
||||
|
||||
// Compile all instructions from [current recompiler PC, end)
|
||||
void compileUntil(const PICAShader& shaderUnit, u32 endPC);
|
||||
// Compile instruction "instr"
|
||||
|
@ -125,7 +132,7 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
|||
PrologueCallback prologueCb = nullptr;
|
||||
|
||||
// Initialize our emitter with "allocSize" bytes of RWX memory
|
||||
ShaderEmitter() : Xbyak::CodeGenerator(allocSize) {
|
||||
ShaderEmitter(bool useSafeMUL) : Xbyak::CodeGenerator(allocSize), useSafeMUL(useSafeMUL) {
|
||||
cpuCaps = Xbyak::util::Cpu();
|
||||
|
||||
haveSSE4_1 = cpuCaps.has(Xbyak::util::Cpu::tSSE41);
|
||||
|
|
|
@ -92,6 +92,9 @@ class GPU {
|
|||
// Set to false by the renderer when the lighting_lut is uploaded ot the GPU
|
||||
bool lightingLUTDirty = false;
|
||||
|
||||
bool fogLUTDirty = false;
|
||||
std::array<uint32_t, 128> fogLUT;
|
||||
|
||||
GPU(Memory& mem, EmulatorConfig& config);
|
||||
void display() { renderer->display(); }
|
||||
void screenshot(const std::string& name) { renderer->screenshot(name); }
|
||||
|
@ -164,7 +167,8 @@ class GPU {
|
|||
u32 index = paddr - PhysicalAddrs::VRAM;
|
||||
return (T*)&vram[index];
|
||||
} else [[unlikely]] {
|
||||
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
|
||||
Helpers::warn("[GPU] Tried to access unknown physical address: %08X", paddr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
258
include/PICA/pica_frag_config.hpp
Normal file
258
include/PICA/pica_frag_config.hpp
Normal file
|
@ -0,0 +1,258 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "PICA/pica_hash.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "bitfield.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace PICA {
|
||||
struct OutputConfig {
|
||||
union {
|
||||
u32 raw{};
|
||||
// Merge the enable + compare function into 1 field to avoid duplicate shaders
|
||||
// enable == off means a CompareFunction of Always
|
||||
BitField<0, 3, CompareFunction> alphaTestFunction;
|
||||
BitField<3, 1, u32> depthMapEnable;
|
||||
};
|
||||
};
|
||||
|
||||
struct TextureConfig {
|
||||
u32 texUnitConfig;
|
||||
u32 texEnvUpdateBuffer;
|
||||
|
||||
// There's 6 TEV stages, and each one is configured via 4 word-sized registers
|
||||
// (+ the constant color register, which we don't include here, otherwise we'd generate too many shaders)
|
||||
std::array<u32, 4 * 6> tevConfigs;
|
||||
};
|
||||
|
||||
struct FogConfig {
|
||||
union {
|
||||
u32 raw{};
|
||||
|
||||
BitField<0, 3, FogMode> mode;
|
||||
BitField<3, 1, u32> flipDepth;
|
||||
BitField<8, 8, u32> fogColorR;
|
||||
BitField<16, 8, u32> fogColorG;
|
||||
BitField<24, 8, u32> fogColorB;
|
||||
};
|
||||
};
|
||||
|
||||
struct Light {
|
||||
union {
|
||||
u16 raw;
|
||||
BitField<0, 3, u16> num;
|
||||
BitField<3, 1, u16> directional;
|
||||
BitField<4, 1, u16> twoSidedDiffuse;
|
||||
BitField<5, 1, u16> distanceAttenuationEnable;
|
||||
BitField<6, 1, u16> spotAttenuationEnable;
|
||||
BitField<7, 1, u16> geometricFactor0;
|
||||
BitField<8, 1, u16> geometricFactor1;
|
||||
BitField<9, 1, u16> shadowEnable;
|
||||
};
|
||||
};
|
||||
|
||||
struct LightingLUTConfig {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 1, u32> enable;
|
||||
BitField<1, 1, u32> absInput;
|
||||
BitField<2, 3, u32> type;
|
||||
BitField<5, 3, u32> scale;
|
||||
};
|
||||
};
|
||||
|
||||
struct LightingConfig {
|
||||
union {
|
||||
u32 raw{};
|
||||
BitField<0, 1, u32> enable;
|
||||
BitField<1, 4, u32> lightNum;
|
||||
BitField<5, 2, u32> bumpMode;
|
||||
BitField<7, 2, u32> bumpSelector;
|
||||
BitField<9, 1, u32> bumpRenorm;
|
||||
BitField<10, 1, u32> clampHighlights;
|
||||
BitField<11, 4, u32> config;
|
||||
BitField<15, 1, u32> enablePrimaryAlpha;
|
||||
BitField<16, 1, u32> enableSecondaryAlpha;
|
||||
BitField<17, 1, u32> enableShadow;
|
||||
BitField<18, 1, u32> shadowPrimary;
|
||||
BitField<19, 1, u32> shadowSecondary;
|
||||
BitField<20, 1, u32> shadowInvert;
|
||||
BitField<21, 1, u32> shadowAlpha;
|
||||
BitField<22, 2, u32> shadowSelector;
|
||||
};
|
||||
|
||||
std::array<LightingLUTConfig, 7> luts{};
|
||||
|
||||
std::array<Light, 8> lights{};
|
||||
|
||||
LightingConfig(const std::array<u32, 0x300>& regs) {
|
||||
// Ignore lighting registers if it's disabled
|
||||
if ((regs[InternalRegs::LightingEnable] & 1) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 config0 = regs[InternalRegs::LightConfig0];
|
||||
const u32 config1 = regs[InternalRegs::LightConfig1];
|
||||
const u32 totalLightCount = Helpers::getBits<0, 3>(regs[InternalRegs::LightNumber]) + 1;
|
||||
|
||||
enable = 1;
|
||||
lightNum = totalLightCount;
|
||||
|
||||
enableShadow = Helpers::getBit<0>(config0);
|
||||
if (enableShadow) [[unlikely]] {
|
||||
shadowPrimary = Helpers::getBit<16>(config0);
|
||||
shadowSecondary = Helpers::getBit<17>(config0);
|
||||
shadowInvert = Helpers::getBit<18>(config0);
|
||||
shadowAlpha = Helpers::getBit<19>(config0);
|
||||
shadowSelector = Helpers::getBits<24, 2>(config0);
|
||||
}
|
||||
|
||||
enablePrimaryAlpha = Helpers::getBit<2>(config0);
|
||||
enableSecondaryAlpha = Helpers::getBit<3>(config0);
|
||||
config = Helpers::getBits<4, 4>(config0);
|
||||
|
||||
bumpSelector = Helpers::getBits<22, 2>(config0);
|
||||
clampHighlights = Helpers::getBit<27>(config0);
|
||||
bumpMode = Helpers::getBits<28, 2>(config0);
|
||||
bumpRenorm = Helpers::getBit<30>(config0) ^ 1; // 0 = enable so flip it with xor
|
||||
|
||||
for (int i = 0; i < totalLightCount; i++) {
|
||||
auto& light = lights[i];
|
||||
light.num = (regs[InternalRegs::LightPermutation] >> (i * 4)) & 0x7;
|
||||
|
||||
const u32 lightConfig = regs[InternalRegs::Light0Config + 0x10 * light.num];
|
||||
light.directional = Helpers::getBit<0>(lightConfig);
|
||||
light.twoSidedDiffuse = Helpers::getBit<1>(lightConfig);
|
||||
light.geometricFactor0 = Helpers::getBit<2>(lightConfig);
|
||||
light.geometricFactor1 = Helpers::getBit<3>(lightConfig);
|
||||
|
||||
light.shadowEnable = ((config1 >> light.num) & 1) ^ 1; // This also does 0 = enabled
|
||||
light.spotAttenuationEnable = ((config1 >> (8 + light.num)) & 1) ^ 1; // Same here
|
||||
light.distanceAttenuationEnable = ((config1 >> (24 + light.num)) & 1) ^ 1; // Of course same here
|
||||
}
|
||||
|
||||
LightingLUTConfig& d0 = luts[Lights::LUT_D0];
|
||||
LightingLUTConfig& d1 = luts[Lights::LUT_D1];
|
||||
LightingLUTConfig& sp = luts[spotlightLutIndex];
|
||||
LightingLUTConfig& fr = luts[Lights::LUT_FR];
|
||||
LightingLUTConfig& rb = luts[Lights::LUT_RB];
|
||||
LightingLUTConfig& rg = luts[Lights::LUT_RG];
|
||||
LightingLUTConfig& rr = luts[Lights::LUT_RR];
|
||||
|
||||
d0.enable = Helpers::getBit<16>(config1) == 0;
|
||||
d1.enable = Helpers::getBit<17>(config1) == 0;
|
||||
fr.enable = Helpers::getBit<19>(config1) == 0;
|
||||
rb.enable = Helpers::getBit<20>(config1) == 0;
|
||||
rg.enable = Helpers::getBit<21>(config1) == 0;
|
||||
rr.enable = Helpers::getBit<22>(config1) == 0;
|
||||
sp.enable = 1;
|
||||
|
||||
const u32 lutAbs = regs[InternalRegs::LightLUTAbs];
|
||||
const u32 lutSelect = regs[InternalRegs::LightLUTSelect];
|
||||
const u32 lutScale = regs[InternalRegs::LightLUTScale];
|
||||
|
||||
if (d0.enable) {
|
||||
d0.absInput = Helpers::getBit<1>(lutAbs) == 0;
|
||||
d0.type = Helpers::getBits<0, 3>(lutSelect);
|
||||
d0.scale = Helpers::getBits<0, 3>(lutScale);
|
||||
}
|
||||
|
||||
if (d1.enable) {
|
||||
d1.absInput = Helpers::getBit<5>(lutAbs) == 0;
|
||||
d1.type = Helpers::getBits<4, 3>(lutSelect);
|
||||
d1.scale = Helpers::getBits<4, 3>(lutScale);
|
||||
}
|
||||
|
||||
sp.absInput = Helpers::getBit<9>(lutAbs) == 0;
|
||||
sp.type = Helpers::getBits<8, 3>(lutSelect);
|
||||
sp.scale = Helpers::getBits<8, 3>(lutScale);
|
||||
|
||||
if (fr.enable) {
|
||||
fr.absInput = Helpers::getBit<13>(lutAbs) == 0;
|
||||
fr.type = Helpers::getBits<12, 3>(lutSelect);
|
||||
fr.scale = Helpers::getBits<12, 3>(lutScale);
|
||||
}
|
||||
|
||||
if (rb.enable) {
|
||||
rb.absInput = Helpers::getBit<17>(lutAbs) == 0;
|
||||
rb.type = Helpers::getBits<16, 3>(lutSelect);
|
||||
rb.scale = Helpers::getBits<16, 3>(lutScale);
|
||||
}
|
||||
|
||||
if (rg.enable) {
|
||||
rg.absInput = Helpers::getBit<21>(lutAbs) == 0;
|
||||
rg.type = Helpers::getBits<20, 3>(lutSelect);
|
||||
rg.scale = Helpers::getBits<20, 3>(lutScale);
|
||||
}
|
||||
|
||||
if (rr.enable) {
|
||||
rr.absInput = Helpers::getBit<25>(lutAbs) == 0;
|
||||
rr.type = Helpers::getBits<24, 3>(lutSelect);
|
||||
rr.scale = Helpers::getBits<24, 3>(lutScale);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Config used for identifying unique fragment pipeline configurations
|
||||
struct FragmentConfig {
|
||||
OutputConfig outConfig;
|
||||
TextureConfig texConfig;
|
||||
FogConfig fogConfig;
|
||||
LightingConfig lighting;
|
||||
|
||||
bool operator==(const FragmentConfig& config) const {
|
||||
// Hash function and equality operator required by std::unordered_map
|
||||
return std::memcmp(this, &config, sizeof(FragmentConfig)) == 0;
|
||||
}
|
||||
|
||||
FragmentConfig(const std::array<u32, 0x300>& regs) : lighting(regs) {
|
||||
auto alphaTestConfig = regs[InternalRegs::AlphaTestConfig];
|
||||
auto alphaTestFunction = Helpers::getBits<4, 3>(alphaTestConfig);
|
||||
|
||||
outConfig.alphaTestFunction =
|
||||
(alphaTestConfig & 1) ? static_cast<PICA::CompareFunction>(alphaTestFunction) : PICA::CompareFunction::Always;
|
||||
outConfig.depthMapEnable = regs[InternalRegs::DepthmapEnable] & 1;
|
||||
|
||||
texConfig.texUnitConfig = regs[InternalRegs::TexUnitCfg];
|
||||
texConfig.texEnvUpdateBuffer = regs[InternalRegs::TexEnvUpdateBuffer];
|
||||
|
||||
// Set up TEV stages. Annoyingly we can't just memcpy as the TEV registers are arranged like
|
||||
// {Source, Operand, Combiner, Color, Scale} and we want to skip the color register since it's uploaded via UBO
|
||||
#define setupTevStage(stage) \
|
||||
std::memcpy(&texConfig.tevConfigs[stage * 4], ®s[InternalRegs::TexEnv##stage##Source], 3 * sizeof(u32)); \
|
||||
texConfig.tevConfigs[stage * 4 + 3] = regs[InternalRegs::TexEnv##stage##Source + 4];
|
||||
|
||||
setupTevStage(0);
|
||||
setupTevStage(1);
|
||||
setupTevStage(2);
|
||||
setupTevStage(3);
|
||||
setupTevStage(4);
|
||||
setupTevStage(5);
|
||||
#undef setupTevStage
|
||||
|
||||
fogConfig.mode = (FogMode)Helpers::getBits<0, 3>(regs[InternalRegs::TexEnvUpdateBuffer]);
|
||||
|
||||
if (fogConfig.mode == FogMode::Fog) {
|
||||
fogConfig.flipDepth = Helpers::getBit<16>(regs[InternalRegs::TexEnvUpdateBuffer]);
|
||||
fogConfig.fogColorR = Helpers::getBits<0, 8>(regs[InternalRegs::FogColor]);
|
||||
fogConfig.fogColorG = Helpers::getBits<8, 8>(regs[InternalRegs::FogColor]);
|
||||
fogConfig.fogColorB = Helpers::getBits<16, 8>(regs[InternalRegs::FogColor]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(
|
||||
std::has_unique_object_representations<OutputConfig>() && std::has_unique_object_representations<TextureConfig>() &&
|
||||
std::has_unique_object_representations<FogConfig>() && std::has_unique_object_representations<Light>()
|
||||
);
|
||||
} // namespace PICA
|
||||
|
||||
// Override std::hash for our fragment config class
|
||||
template <>
|
||||
struct std::hash<PICA::FragmentConfig> {
|
||||
std::size_t operator()(const PICA::FragmentConfig& config) const noexcept { return PICAHash::computeHash((const char*)&config, sizeof(config)); }
|
||||
};
|
45
include/PICA/pica_frag_uniforms.hpp
Normal file
45
include/PICA/pica_frag_uniforms.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace PICA {
|
||||
struct LightUniform {
|
||||
using vec3 = std::array<float, 3>;
|
||||
|
||||
// std140 requires vec3s be aligned to 16 bytes
|
||||
alignas(16) vec3 specular0;
|
||||
alignas(16) vec3 specular1;
|
||||
alignas(16) vec3 diffuse;
|
||||
alignas(16) vec3 ambient;
|
||||
alignas(16) vec3 position;
|
||||
alignas(16) vec3 spotlightDirection;
|
||||
|
||||
float distanceAttenuationBias;
|
||||
float distanceAttenuationScale;
|
||||
};
|
||||
|
||||
struct FragmentUniforms {
|
||||
using vec3 = std::array<float, 3>;
|
||||
using vec4 = std::array<float, 4>;
|
||||
static constexpr usize tevStageCount = 6;
|
||||
|
||||
s32 alphaReference;
|
||||
float depthScale;
|
||||
float depthOffset;
|
||||
|
||||
alignas(16) vec4 constantColors[tevStageCount];
|
||||
alignas(16) vec4 tevBufferColor;
|
||||
alignas(16) vec4 clipCoords;
|
||||
|
||||
// Note: We upload this as a u32 and decode on GPU
|
||||
u32 globalAmbientLight;
|
||||
// NOTE: THIS MUST BE LAST so that if lighting is disabled we can potentially omit uploading it
|
||||
LightUniform lightUniforms[8];
|
||||
};
|
||||
|
||||
// Assert that lightUniforms is the last member of the structure
|
||||
static_assert(offsetof(FragmentUniforms, lightUniforms) + 8 * sizeof(LightUniform) == sizeof(FragmentUniforms));
|
||||
} // namespace PICA
|
|
@ -51,6 +51,18 @@ namespace PICA {
|
|||
#undef defineTexEnv
|
||||
// clang-format on
|
||||
|
||||
// Fog registers
|
||||
FogColor = 0xE1,
|
||||
FogLUTIndex = 0xE6,
|
||||
FogLUTData0 = 0xE8,
|
||||
FogLUTData1 = 0xE9,
|
||||
FogLUTData2 = 0xEA,
|
||||
FogLUTData3 = 0xEB,
|
||||
FogLUTData4 = 0xEC,
|
||||
FogLUTData5 = 0xED,
|
||||
FogLUTData6 = 0xEE,
|
||||
FogLUTData7 = 0xEF,
|
||||
|
||||
// Framebuffer registers
|
||||
ColourOperation = 0x100,
|
||||
BlendFunc = 0x101,
|
||||
|
@ -67,7 +79,29 @@ namespace PICA {
|
|||
ColourBufferLoc = 0x11D,
|
||||
FramebufferSize = 0x11E,
|
||||
|
||||
//LightingRegs
|
||||
// Lighting registers
|
||||
LightingEnable = 0x8F,
|
||||
Light0Specular0 = 0x140,
|
||||
Light0Specular1 = 0x141,
|
||||
Light0Diffuse = 0x142,
|
||||
Light0Ambient = 0x143,
|
||||
Light0XY = 0x144,
|
||||
Light0Z = 0x145,
|
||||
Light0SpotlightXY = 0x146,
|
||||
Light0SpotlightZ = 0x147,
|
||||
Light0Config = 0x149,
|
||||
Light0AttenuationBias = 0x14A,
|
||||
Light0AttenuationScale = 0x14B,
|
||||
|
||||
LightGlobalAmbient = 0x1C0,
|
||||
LightNumber = 0x1C2,
|
||||
LightConfig0 = 0x1C3,
|
||||
LightConfig1 = 0x1C4,
|
||||
LightPermutation = 0x1D9,
|
||||
LightLUTAbs = 0x1D0,
|
||||
LightLUTSelect = 0x1D1,
|
||||
LightLUTScale = 0x1D2,
|
||||
|
||||
LightingLUTIndex = 0x01C5,
|
||||
LightingLUTData0 = 0x01C8,
|
||||
LightingLUTData1 = 0x01C9,
|
||||
|
@ -231,7 +265,8 @@ namespace PICA {
|
|||
enum : u32 {
|
||||
LUT_D0 = 0,
|
||||
LUT_D1,
|
||||
LUT_FR,
|
||||
// LUT 2 is not used, the emulator internally uses it for referring to the current source's spotlight in shaders
|
||||
LUT_FR = 0x3,
|
||||
LUT_RB,
|
||||
LUT_RG,
|
||||
LUT_RR,
|
||||
|
@ -255,6 +290,11 @@ namespace PICA {
|
|||
};
|
||||
}
|
||||
|
||||
// There's actually 8 different LUTs (SP0-SP7), one for each light with different indices (8-15)
|
||||
// We use an unused LUT value for "this light source's spotlight" instead and figure out which light source to use in compileLutLookup
|
||||
// This is particularly intuitive in several places, such as checking if a LUT is enabled
|
||||
static constexpr int spotlightLutIndex = 2;
|
||||
|
||||
enum class TextureFmt : u32 {
|
||||
RGBA8 = 0x0,
|
||||
RGB8 = 0x1,
|
||||
|
@ -345,4 +385,137 @@ namespace PICA {
|
|||
GeometryPrimitive = 3,
|
||||
};
|
||||
|
||||
enum class CompareFunction : u32 {
|
||||
Never = 0,
|
||||
Always = 1,
|
||||
Equal = 2,
|
||||
NotEqual = 3,
|
||||
Less = 4,
|
||||
LessOrEqual = 5,
|
||||
Greater = 6,
|
||||
GreaterOrEqual = 7,
|
||||
};
|
||||
|
||||
enum class FogMode : u32 {
|
||||
Disabled = 0,
|
||||
Fog = 5,
|
||||
Gas = 7,
|
||||
};
|
||||
|
||||
struct TexEnvConfig {
|
||||
enum class Source : u8 {
|
||||
PrimaryColor = 0x0,
|
||||
PrimaryFragmentColor = 0x1,
|
||||
SecondaryFragmentColor = 0x2,
|
||||
Texture0 = 0x3,
|
||||
Texture1 = 0x4,
|
||||
Texture2 = 0x5,
|
||||
Texture3 = 0x6,
|
||||
// TODO: Inbetween values are unknown
|
||||
PreviousBuffer = 0xD,
|
||||
Constant = 0xE,
|
||||
Previous = 0xF,
|
||||
};
|
||||
|
||||
enum class ColorOperand : u8 {
|
||||
SourceColor = 0x0,
|
||||
OneMinusSourceColor = 0x1,
|
||||
SourceAlpha = 0x2,
|
||||
OneMinusSourceAlpha = 0x3,
|
||||
SourceRed = 0x4,
|
||||
OneMinusSourceRed = 0x5,
|
||||
// TODO: Inbetween values are unknown
|
||||
SourceGreen = 0x8,
|
||||
OneMinusSourceGreen = 0x9,
|
||||
// Inbetween values are unknown
|
||||
SourceBlue = 0xC,
|
||||
OneMinusSourceBlue = 0xD,
|
||||
};
|
||||
|
||||
enum class AlphaOperand : u8 {
|
||||
SourceAlpha = 0x0,
|
||||
OneMinusSourceAlpha = 0x1,
|
||||
SourceRed = 0x2,
|
||||
OneMinusSourceRed = 0x3,
|
||||
SourceGreen = 0x4,
|
||||
OneMinusSourceGreen = 0x5,
|
||||
SourceBlue = 0x6,
|
||||
OneMinusSourceBlue = 0x7,
|
||||
};
|
||||
|
||||
enum class Operation : u8 {
|
||||
Replace = 0,
|
||||
Modulate = 1,
|
||||
Add = 2,
|
||||
AddSigned = 3,
|
||||
Lerp = 4,
|
||||
Subtract = 5,
|
||||
Dot3RGB = 6,
|
||||
Dot3RGBA = 7,
|
||||
MultiplyAdd = 8,
|
||||
AddMultiply = 9,
|
||||
};
|
||||
|
||||
// RGB sources
|
||||
Source colorSource1, colorSource2, colorSource3;
|
||||
// Alpha sources
|
||||
Source alphaSource1, alphaSource2, alphaSource3;
|
||||
|
||||
// RGB operands
|
||||
ColorOperand colorOperand1, colorOperand2, colorOperand3;
|
||||
// Alpha operands
|
||||
AlphaOperand alphaOperand1, alphaOperand2, alphaOperand3;
|
||||
|
||||
// Texture environment operations for this stage
|
||||
Operation colorOp, alphaOp;
|
||||
|
||||
u32 constColor;
|
||||
|
||||
private:
|
||||
// These are the only private members since their value doesn't actually reflect the scale
|
||||
// So we make them public so we'll always use the appropriate member functions instead
|
||||
u8 colorScale;
|
||||
u8 alphaScale;
|
||||
|
||||
public:
|
||||
// Create texture environment object from TEV registers
|
||||
TexEnvConfig(u32 source, u32 operand, u32 combiner, u32 color, u32 scale) : constColor(color) {
|
||||
colorSource1 = Helpers::getBits<0, 4, Source>(source);
|
||||
colorSource2 = Helpers::getBits<4, 4, Source>(source);
|
||||
colorSource3 = Helpers::getBits<8, 4, Source>(source);
|
||||
|
||||
alphaSource1 = Helpers::getBits<16, 4, Source>(source);
|
||||
alphaSource2 = Helpers::getBits<20, 4, Source>(source);
|
||||
alphaSource3 = Helpers::getBits<24, 4, Source>(source);
|
||||
|
||||
colorOperand1 = Helpers::getBits<0, 4, ColorOperand>(operand);
|
||||
colorOperand2 = Helpers::getBits<4, 4, ColorOperand>(operand);
|
||||
colorOperand3 = Helpers::getBits<8, 4, ColorOperand>(operand);
|
||||
|
||||
alphaOperand1 = Helpers::getBits<12, 3, AlphaOperand>(operand);
|
||||
alphaOperand2 = Helpers::getBits<16, 3, AlphaOperand>(operand);
|
||||
alphaOperand3 = Helpers::getBits<20, 3, AlphaOperand>(operand);
|
||||
|
||||
colorOp = Helpers::getBits<0, 4, Operation>(combiner);
|
||||
alphaOp = Helpers::getBits<16, 4, Operation>(combiner);
|
||||
|
||||
colorScale = Helpers::getBits<0, 2>(scale);
|
||||
alphaScale = Helpers::getBits<16, 2>(scale);
|
||||
}
|
||||
|
||||
u32 getColorScale() { return (colorScale <= 2) ? (1 << colorScale) : 1; }
|
||||
u32 getAlphaScale() { return (alphaScale <= 2) ? (1 << alphaScale) : 1; }
|
||||
|
||||
bool isPassthroughStage() {
|
||||
// clang-format off
|
||||
// Thank you to the Citra dev that wrote this out
|
||||
return (
|
||||
colorOp == Operation::Replace && alphaOp == Operation::Replace &&
|
||||
colorSource1 == Source::Previous && alphaSource1 == Source::Previous &&
|
||||
colorOperand1 == ColorOperand::SourceColor && alphaOperand1 == AlphaOperand::SourceAlpha &&
|
||||
getColorScale() == 1 && getAlphaScale() == 1
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
};
|
||||
} // namespace PICA
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "PICA/float_types.hpp"
|
||||
|
@ -90,9 +92,12 @@ class PICAShader {
|
|||
public:
|
||||
// These are placed close to the temp registers and co because it helps the JIT generate better code
|
||||
u32 entrypoint = 0; // Initial shader PC
|
||||
u32 boolUniform;
|
||||
std::array<std::array<u8, 4>, 4> intUniforms;
|
||||
|
||||
// We want these registers in this order & with this alignment for uploading them directly to a UBO
|
||||
// When emulating shaders on the GPU. Plus this alignment for float uniforms is necessary for doing SIMD in the shader->CPU recompilers.
|
||||
alignas(16) std::array<vec4f, 96> floatUniforms;
|
||||
alignas(16) std::array<std::array<u8, 4>, 4> intUniforms;
|
||||
u32 boolUniform;
|
||||
|
||||
alignas(16) std::array<vec4f, 16> fixedAttributes; // Fixed vertex attributes
|
||||
alignas(16) std::array<vec4f, 16> inputs; // Attributes passed to the shader
|
||||
|
@ -220,13 +225,9 @@ class PICAShader {
|
|||
public:
|
||||
static constexpr size_t maxInstructionCount = 4096;
|
||||
std::array<u32, maxInstructionCount> loadedShader; // Currently loaded & active shader
|
||||
std::array<u32, maxInstructionCount> bufferedShader; // Shader to be transferred when the SH_CODETRANSFER_END reg gets written to
|
||||
|
||||
PICAShader(ShaderType type) : type(type) {}
|
||||
|
||||
// Theese functions are in the header to be inlined more easily, though with LTO I hope I'll be able to move them
|
||||
void finalize() { std::memcpy(&loadedShader[0], &bufferedShader[0], 4096 * sizeof(u32)); }
|
||||
|
||||
void setBufferIndex(u32 index) { bufferIndex = index & 0xfff; }
|
||||
void setOpDescriptorIndex(u32 index) { opDescriptorIndex = index & 0x7f; }
|
||||
|
||||
|
@ -235,7 +236,7 @@ class PICAShader {
|
|||
Helpers::panic("o no, shader upload overflew");
|
||||
}
|
||||
|
||||
bufferedShader[bufferIndex++] = word;
|
||||
loadedShader[bufferIndex++] = word;
|
||||
bufferIndex &= 0xfff;
|
||||
|
||||
codeHashDirty = true; // Signal the JIT if necessary that the program hash has potentially changed
|
||||
|
@ -295,4 +296,9 @@ class PICAShader {
|
|||
|
||||
Hash getCodeHash();
|
||||
Hash getOpdescHash();
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(
|
||||
offsetof(PICAShader, intUniforms) == offsetof(PICAShader, floatUniforms) + 96 * sizeof(float) * 4 &&
|
||||
offsetof(PICAShader, boolUniform) == offsetof(PICAShader, intUniforms) + 4 * sizeof(u8) * 4
|
||||
);
|
112
include/PICA/shader_decompiler.hpp
Normal file
112
include/PICA/shader_decompiler.hpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "PICA/shader.hpp"
|
||||
#include "PICA/shader_gen_types.hpp"
|
||||
|
||||
struct EmulatorConfig;
|
||||
|
||||
namespace PICA::ShaderGen {
|
||||
// Control flow analysis is partially based on
|
||||
// https://github.com/PabloMK7/citra/blob/d0179559466ff09731d74474322ee880fbb44b00/src/video_core/shader/generator/glsl_shader_decompiler.cpp#L33
|
||||
struct ControlFlow {
|
||||
// A continuous range of addresses
|
||||
struct AddressRange {
|
||||
u32 start, end;
|
||||
AddressRange(u32 start, u32 end) : start(start), end(end) {}
|
||||
|
||||
// Use lexicographic comparison for functions in order to sort them in a set
|
||||
bool operator<(const AddressRange& other) const { return std::tie(start, end) < std::tie(other.start, other.end); }
|
||||
};
|
||||
|
||||
struct Function {
|
||||
using Labels = std::set<u32>;
|
||||
|
||||
enum class ExitMode {
|
||||
Unknown, // Can't guarantee whether we'll exit properly, fall back to CPU shaders (can happen with jmp shenanigans)
|
||||
AlwaysReturn, // All paths reach the return point.
|
||||
Conditional, // One or more code paths reach the return point or an END instruction conditionally.
|
||||
AlwaysEnd, // All paths reach an END instruction.
|
||||
};
|
||||
|
||||
u32 start; // Starting PC of the function
|
||||
u32 end; // End PC of the function
|
||||
Labels outLabels{}; // Labels this function can "goto" (jump) to
|
||||
ExitMode exitMode = ExitMode::Unknown;
|
||||
|
||||
explicit Function(u32 start, u32 end) : start(start), end(end) {}
|
||||
bool operator<(const Function& other) const { return AddressRange(start, end) < AddressRange(other.start, other.end); }
|
||||
|
||||
std::string getIdentifier() const { return "func_" + std::to_string(start) + "_to_" + std::to_string(end); }
|
||||
std::string getForwardDecl() const { return "void " + getIdentifier() + "();\n"; }
|
||||
std::string getCallStatement() const { return getIdentifier() + "()"; }
|
||||
};
|
||||
|
||||
std::set<Function> functions{};
|
||||
std::map<AddressRange, Function::ExitMode> exitMap{};
|
||||
|
||||
// Tells us whether analysis of the shader we're trying to compile failed, in which case we'll need to fail back to shader emulation
|
||||
// On the CPU
|
||||
bool analysisFailed = false;
|
||||
|
||||
// This will recursively add all functions called by the function too, as analyzeFunction will call addFunction on control flow instructions
|
||||
const Function* addFunction(const PICAShader& shader, u32 start, u32 end) {
|
||||
auto searchIterator = functions.find(Function(start, end));
|
||||
if (searchIterator != functions.end()) {
|
||||
return &(*searchIterator);
|
||||
}
|
||||
|
||||
// Add this function and analyze it if it doesn't already exist
|
||||
Function function(start, end);
|
||||
function.exitMode = analyzeFunction(shader, start, end, function.outLabels);
|
||||
|
||||
// This function could not be fully analyzed, report failure
|
||||
if (function.exitMode == Function::ExitMode::Unknown) {
|
||||
analysisFailed = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Add function to our function list
|
||||
auto [it, added] = functions.insert(std::move(function));
|
||||
return &(*it);
|
||||
}
|
||||
|
||||
void analyze(const PICAShader& shader, u32 entrypoint);
|
||||
Function::ExitMode analyzeFunction(const PICAShader& shader, u32 start, u32 end, Function::Labels& labels);
|
||||
};
|
||||
|
||||
class ShaderDecompiler {
|
||||
using AddressRange = ControlFlow::AddressRange;
|
||||
using Function = ControlFlow::Function;
|
||||
|
||||
ControlFlow controlFlow{};
|
||||
|
||||
PICAShader& shader;
|
||||
EmulatorConfig& config;
|
||||
std::string decompiledShader;
|
||||
|
||||
u32 entrypoint;
|
||||
|
||||
API api;
|
||||
Language language;
|
||||
|
||||
void compileInstruction(u32& pc, bool& finished);
|
||||
void compileRange(const AddressRange& range);
|
||||
void callFunction(const Function& function);
|
||||
const Function* findFunction(const AddressRange& range);
|
||||
|
||||
void writeAttributes();
|
||||
|
||||
public:
|
||||
ShaderDecompiler(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language)
|
||||
: shader(shader), entrypoint(entrypoint), config(config), api(api), language(language), decompiledShader("") {}
|
||||
|
||||
std::string decompile();
|
||||
};
|
||||
|
||||
std::string decompileShader(PICAShader& shader, EmulatorConfig& config, u32 entrypoint, API api, Language language);
|
||||
} // namespace PICA::ShaderGen
|
39
include/PICA/shader_gen.hpp
Normal file
39
include/PICA/shader_gen.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "PICA/gpu.hpp"
|
||||
#include "PICA/pica_frag_config.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "PICA/shader_gen_types.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace PICA::ShaderGen {
|
||||
class FragmentGenerator {
|
||||
API api;
|
||||
Language language;
|
||||
|
||||
void compileTEV(std::string& shader, int stage, const PICA::FragmentConfig& config);
|
||||
void getSource(std::string& shader, PICA::TexEnvConfig::Source source, int index, const PICA::FragmentConfig& config);
|
||||
void getColorOperand(std::string& shader, PICA::TexEnvConfig::Source source, PICA::TexEnvConfig::ColorOperand color, int index, const PICA::FragmentConfig& config);
|
||||
void getAlphaOperand(std::string& shader, PICA::TexEnvConfig::Source source, PICA::TexEnvConfig::AlphaOperand alpha, int index, const PICA::FragmentConfig& config);
|
||||
void getColorOperation(std::string& shader, PICA::TexEnvConfig::Operation op);
|
||||
void getAlphaOperation(std::string& shader, PICA::TexEnvConfig::Operation op);
|
||||
|
||||
void applyAlphaTest(std::string& shader, const PICA::FragmentConfig& config);
|
||||
void compileLights(std::string& shader, const PICA::FragmentConfig& config);
|
||||
void compileLUTLookup(std::string& shader, const PICA::FragmentConfig& config, u32 lightIndex, u32 lutID);
|
||||
bool isSamplerEnabled(u32 environmentID, u32 lutID);
|
||||
|
||||
void compileFog(std::string& shader, const PICA::FragmentConfig& config);
|
||||
|
||||
public:
|
||||
FragmentGenerator(API api, Language language) : api(api), language(language) {}
|
||||
std::string generate(const PICA::FragmentConfig& config);
|
||||
std::string getDefaultVertexShader();
|
||||
|
||||
void setTarget(API api, Language language) {
|
||||
this->api = api;
|
||||
this->language = language;
|
||||
}
|
||||
};
|
||||
}; // namespace PICA::ShaderGen
|
9
include/PICA/shader_gen_types.hpp
Normal file
9
include/PICA/shader_gen_types.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
namespace PICA::ShaderGen {
|
||||
// Graphics API this shader is targetting
|
||||
enum class API { GL, GLES, Vulkan };
|
||||
|
||||
// Shading language to use (Only GLSL for the time being)
|
||||
enum class Language { GLSL };
|
||||
} // namespace PICA::ShaderGen
|
|
@ -7,14 +7,29 @@
|
|||
// Remember to initialize every field here to its default value otherwise bad things will happen
|
||||
struct EmulatorConfig {
|
||||
// Only enable the shader JIT by default on platforms where it's completely tested
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
#if defined(PANDA3DS_X64_HOST) || defined(PANDA3DS_ARM64_HOST)
|
||||
static constexpr bool shaderJitDefault = true;
|
||||
#else
|
||||
static constexpr bool shaderJitDefault = false;
|
||||
#endif
|
||||
|
||||
// For now, use specialized shaders by default on MacOS as M1 drivers are buggy when using the ubershader, and on Android since mobile GPUs are
|
||||
// horrible. On other platforms we default to ubershader + shadergen fallback for lights
|
||||
#if defined(__ANDROID__) || defined(__APPLE__)
|
||||
static constexpr bool ubershaderDefault = false;
|
||||
#else
|
||||
static constexpr bool ubershaderDefault = true;
|
||||
#endif
|
||||
|
||||
bool shaderJitEnabled = shaderJitDefault;
|
||||
bool discordRpcEnabled = false;
|
||||
bool useUbershaders = ubershaderDefault;
|
||||
bool accurateShaderMul = false;
|
||||
|
||||
// Toggles whether to force shadergen when there's more than N lights active and we're using the ubershader, for better performance
|
||||
bool forceShadergenForLights = true;
|
||||
int lightShadergenThreshold = 1;
|
||||
|
||||
RendererType rendererType = RendererType::OpenGL;
|
||||
Audio::DSPCore::Type dspType = Audio::DSPCore::Type::Null;
|
||||
|
||||
|
@ -36,4 +51,4 @@ struct EmulatorConfig {
|
|||
EmulatorConfig(const std::filesystem::path& path);
|
||||
void load();
|
||||
void save();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ struct NCCH {
|
|||
|
||||
static constexpr u64 mediaUnit = 0x200;
|
||||
u64 size = 0; // Size of NCCH converted to bytes
|
||||
u64 saveDataSize = 0;
|
||||
u32 stackSize = 0;
|
||||
u32 bssSize = 0;
|
||||
u32 exheaderSize = 0;
|
||||
|
@ -60,10 +61,10 @@ struct NCCH {
|
|||
CodeSetInfo text, data, rodata;
|
||||
FSInfo partitionInfo;
|
||||
|
||||
std::optional<Crypto::AESKey> primaryKey, secondaryKey;
|
||||
|
||||
// Contents of the .code file in the ExeFS
|
||||
std::vector<u8> codeFile;
|
||||
// Contains of the cart's save data
|
||||
std::vector<u8> saveData;
|
||||
// The cart region. Only the CXI's region matters to us. Necessary to get past region locking
|
||||
std::optional<Regions> region = std::nullopt;
|
||||
std::vector<u8> smdh;
|
||||
|
@ -76,7 +77,7 @@ struct NCCH {
|
|||
bool hasExeFS() { return exeFS.size != 0; }
|
||||
bool hasRomFS() { return romFS.size != 0; }
|
||||
bool hasCode() { return codeFile.size() != 0; }
|
||||
bool hasSaveData() { return saveData.size() != 0; }
|
||||
bool hasSaveData() { return saveDataSize != 0; }
|
||||
|
||||
// Parse SMDH for region info and such. Returns false on failure, true on success
|
||||
bool parseSMDH(const std::vector<u8> &smdh);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "panda_qt/config_window.hpp"
|
||||
#include "panda_qt/patch_window.hpp"
|
||||
#include "panda_qt/screen.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
#include "panda_qt/text_editor.hpp"
|
||||
#include "services/hid.hpp"
|
||||
|
||||
|
@ -48,6 +49,7 @@ class MainWindow : public QMainWindow {
|
|||
EditCheat,
|
||||
PressTouchscreen,
|
||||
ReleaseTouchscreen,
|
||||
ReloadUbershader,
|
||||
};
|
||||
|
||||
// Tagged union representing our message queue messages
|
||||
|
@ -99,6 +101,7 @@ class MainWindow : public QMainWindow {
|
|||
CheatsWindow* cheatsEditor;
|
||||
TextEditorWindow* luaEditor;
|
||||
PatchWindow* patchWindow;
|
||||
ShaderEditorWindow* shaderEditor;
|
||||
|
||||
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||
SDL_GameController* gameController = nullptr;
|
||||
|
@ -110,9 +113,6 @@ class MainWindow : public QMainWindow {
|
|||
void selectROM();
|
||||
void dumpDspFirmware();
|
||||
void dumpRomFS();
|
||||
void openLuaEditor();
|
||||
void openCheatsEditor();
|
||||
void openPatchWindow();
|
||||
void showAboutMenu();
|
||||
void initControllers();
|
||||
void pollControllers();
|
||||
|
@ -140,5 +140,6 @@ class MainWindow : public QMainWindow {
|
|||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
void loadLuaScript(const std::string& code);
|
||||
void reloadShader(const std::string& shader);
|
||||
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
|
||||
};
|
||||
|
|
27
include/panda_qt/shader_editor.hpp
Normal file
27
include/panda_qt/shader_editor.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
#include <string>
|
||||
|
||||
#include "zep.h"
|
||||
#include "zep/mode_repl.h"
|
||||
#include "zep/regress.h"
|
||||
|
||||
class ShaderEditorWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Zep::ZepWidget_Qt zepWidget;
|
||||
Zep::IZepReplProvider replProvider;
|
||||
static constexpr float fontSize = 14.0f;
|
||||
|
||||
public:
|
||||
// Whether this backend supports shader editor
|
||||
bool supported = true;
|
||||
|
||||
ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText);
|
||||
void setText(const std::string& text) { zepWidget.GetEditor().GetMRUBuffer()->SetText(text); }
|
||||
void setEnable(bool enable);
|
||||
};
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
|
@ -20,6 +22,7 @@ enum class RendererType : s8 {
|
|||
Metal = 4,
|
||||
};
|
||||
|
||||
struct EmulatorConfig;
|
||||
class GPU;
|
||||
struct SDL_Window;
|
||||
|
||||
|
@ -46,6 +49,8 @@ class Renderer {
|
|||
u32 outputWindowWidth = 400;
|
||||
u32 outputWindowHeight = 240 * 2;
|
||||
|
||||
EmulatorConfig* emulatorConfig = nullptr;
|
||||
|
||||
public:
|
||||
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
virtual ~Renderer();
|
||||
|
@ -67,6 +72,15 @@ class Renderer {
|
|||
// This function does things like write back or cache necessary state before we delete our context
|
||||
virtual void deinitGraphicsContext() = 0;
|
||||
|
||||
// Functions for hooking up the renderer core to the frontend's shader editor for editing ubershaders in real time
|
||||
// SupportsShaderReload: Indicates whether the backend offers ubershader reload support or not
|
||||
// GetUbershader/SetUbershader: Gets or sets the renderer's current ubershader
|
||||
virtual bool supportsShaderReload() { return false; }
|
||||
virtual std::string getUbershader() { return ""; }
|
||||
virtual void setUbershader(const std::string& shader) {}
|
||||
|
||||
virtual void setUbershaderSetting(bool value) {}
|
||||
|
||||
// Functions for initializing the graphics context for the Qt frontend, where we don't have the convenience of SDL_Window
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
virtual void initGraphicsContext(GL::Context* context) { Helpers::panic("Tried to initialize incompatible renderer with GL context"); }
|
||||
|
@ -92,4 +106,6 @@ class Renderer {
|
|||
outputWindowWidth = width;
|
||||
outputWindowHeight = height;
|
||||
}
|
||||
|
||||
void setConfig(EmulatorConfig* config) { emulatorConfig = config; }
|
||||
};
|
||||
|
|
|
@ -40,9 +40,13 @@ struct GLStateManager {
|
|||
GLuint boundVAO;
|
||||
GLuint boundVBO;
|
||||
GLuint currentProgram;
|
||||
GLuint boundUBO;
|
||||
|
||||
GLenum depthFunc;
|
||||
GLenum logicOp;
|
||||
GLenum blendEquationRGB, blendEquationAlpha;
|
||||
GLenum blendFuncSourceRGB, blendFuncSourceAlpha;
|
||||
GLenum blendFuncDestRGB, blendFuncDestAlpha;
|
||||
|
||||
void reset();
|
||||
void resetBlend();
|
||||
|
@ -51,7 +55,7 @@ struct GLStateManager {
|
|||
void resetColourMask();
|
||||
void resetDepth();
|
||||
void resetVAO();
|
||||
void resetVBO();
|
||||
void resetBuffers();
|
||||
void resetProgram();
|
||||
void resetScissor();
|
||||
void resetStencil();
|
||||
|
@ -183,6 +187,13 @@ struct GLStateManager {
|
|||
}
|
||||
}
|
||||
|
||||
void bindUBO(GLuint handle) {
|
||||
if (boundUBO != handle) {
|
||||
boundUBO = handle;
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, boundUBO);
|
||||
}
|
||||
}
|
||||
|
||||
void bindVAO(const OpenGL::VertexArray& vao) { bindVAO(vao.handle()); }
|
||||
void bindVBO(const OpenGL::VertexBuffer& vbo) { bindVBO(vbo.handle()); }
|
||||
void useProgram(const OpenGL::Program& program) { useProgram(program.handle()); }
|
||||
|
@ -224,6 +235,41 @@ struct GLStateManager {
|
|||
}
|
||||
|
||||
void setDepthFunc(OpenGL::DepthFunc func) { setDepthFunc(static_cast<GLenum>(func)); }
|
||||
|
||||
// Counterpart to glBlendEquationSeparate
|
||||
void setBlendEquation(GLenum modeRGB, GLenum modeAlpha) {
|
||||
if (blendEquationRGB != modeRGB || blendEquationAlpha != modeAlpha) {
|
||||
blendEquationRGB = modeRGB;
|
||||
blendEquationAlpha = modeAlpha;
|
||||
|
||||
glBlendEquationSeparate(modeRGB, modeAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
// Counterpart to glBlendFuncSeparate
|
||||
void setBlendFunc(GLenum sourceRGB, GLenum destRGB, GLenum sourceAlpha, GLenum destAlpha) {
|
||||
if (blendFuncSourceRGB != sourceRGB || blendFuncDestRGB != destRGB || blendFuncSourceAlpha != sourceAlpha ||
|
||||
blendFuncDestAlpha != destAlpha) {
|
||||
|
||||
blendFuncSourceRGB = sourceRGB;
|
||||
blendFuncDestRGB = destRGB;
|
||||
blendFuncSourceAlpha = sourceAlpha;
|
||||
blendFuncDestAlpha = destAlpha;
|
||||
|
||||
glBlendFuncSeparate(sourceRGB, destRGB,sourceAlpha, destAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
// Counterpart to regular glBlendEquation
|
||||
void setBlendEquation(GLenum mode) { setBlendEquation(mode, mode); }
|
||||
|
||||
void setBlendEquation(OpenGL::BlendEquation modeRGB, OpenGL::BlendEquation modeAlpha) {
|
||||
setBlendEquation(static_cast<GLenum>(modeRGB), static_cast<GLenum>(modeAlpha));
|
||||
}
|
||||
|
||||
void setBlendEquation(OpenGL::BlendEquation mode) {
|
||||
setBlendEquation(static_cast<GLenum>(mode));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_constructible<GLStateManager>(), "OpenGL State Manager class is not trivially constructible!");
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "PICA/float_types.hpp"
|
||||
#include "PICA/pica_frag_config.hpp"
|
||||
#include "PICA/pica_hash.hpp"
|
||||
#include "PICA/pica_vertex.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "PICA/shader_gen.hpp"
|
||||
#include "gl_state.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
|
@ -24,21 +30,25 @@ class RendererGL final : public Renderer {
|
|||
|
||||
OpenGL::VertexArray vao;
|
||||
OpenGL::VertexBuffer vbo;
|
||||
bool enableUbershader = true;
|
||||
|
||||
// TEV configuration uniform locations
|
||||
GLint textureEnvSourceLoc = -1;
|
||||
GLint textureEnvOperandLoc = -1;
|
||||
GLint textureEnvCombinerLoc = -1;
|
||||
GLint textureEnvColorLoc = -1;
|
||||
GLint textureEnvScaleLoc = -1;
|
||||
// Data
|
||||
struct {
|
||||
// TEV configuration uniform locations
|
||||
GLint textureEnvSourceLoc = -1;
|
||||
GLint textureEnvOperandLoc = -1;
|
||||
GLint textureEnvCombinerLoc = -1;
|
||||
GLint textureEnvColorLoc = -1;
|
||||
GLint textureEnvScaleLoc = -1;
|
||||
|
||||
// Uniform of PICA registers
|
||||
GLint picaRegLoc = -1;
|
||||
// Uniform of PICA registers
|
||||
GLint picaRegLoc = -1;
|
||||
|
||||
// Depth configuration uniform locations
|
||||
GLint depthOffsetLoc = -1;
|
||||
GLint depthScaleLoc = -1;
|
||||
GLint depthmapEnableLoc = -1;
|
||||
// Depth configuration uniform locations
|
||||
GLint depthOffsetLoc = -1;
|
||||
GLint depthScaleLoc = -1;
|
||||
GLint depthmapEnableLoc = -1;
|
||||
} ubershaderData;
|
||||
|
||||
float oldDepthScale = -1.0;
|
||||
float oldDepthOffset = 0.0;
|
||||
|
@ -53,25 +63,39 @@ class RendererGL final : public Renderer {
|
|||
OpenGL::VertexBuffer dummyVBO;
|
||||
|
||||
OpenGL::Texture screenTexture;
|
||||
GLuint lightLUTTextureArray;
|
||||
OpenGL::Texture LUTTexture;
|
||||
OpenGL::Framebuffer screenFramebuffer;
|
||||
OpenGL::Texture blankTexture;
|
||||
// The "default" vertex shader to use when using specialized shaders but not PICA vertex shader -> GLSL recompilation
|
||||
// We can compile this once and then link it with all other generated fragment shaders
|
||||
OpenGL::Shader defaultShadergenVs;
|
||||
|
||||
// Cached recompiled fragment shader
|
||||
struct CachedProgram {
|
||||
OpenGL::Program program;
|
||||
uint uboBinding;
|
||||
};
|
||||
std::unordered_map<PICA::FragmentConfig, CachedProgram> shaderCache;
|
||||
|
||||
OpenGL::Framebuffer getColourFBO();
|
||||
OpenGL::Texture getTexture(Texture& tex);
|
||||
OpenGL::Program& getSpecializedShader();
|
||||
|
||||
PICA::ShaderGen::FragmentGenerator fragShaderGen;
|
||||
|
||||
MAKE_LOG_FUNCTION(log, rendererLogger)
|
||||
void setupBlending();
|
||||
void setupStencilTest(bool stencilEnable);
|
||||
void bindDepthBuffer();
|
||||
void setupTextureEnvState();
|
||||
void setupUbershaderTexEnv();
|
||||
void bindTexturesToSlots();
|
||||
void updateLightingLUT();
|
||||
void updateFogLUT();
|
||||
void initGraphicsContextInternal();
|
||||
|
||||
public:
|
||||
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
: Renderer(gpu, internalRegs, externalRegs), fragShaderGen(PICA::ShaderGen::API::GL, PICA::ShaderGen::Language::GLSL) {}
|
||||
~RendererGL() override;
|
||||
|
||||
void reset() override;
|
||||
|
@ -82,12 +106,20 @@ class RendererGL final : public Renderer {
|
|||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
|
||||
void deinitGraphicsContext() override;
|
||||
|
||||
virtual bool supportsShaderReload() override { return true; }
|
||||
virtual std::string getUbershader() override;
|
||||
virtual void setUbershader(const std::string& shader) override;
|
||||
|
||||
virtual void setUbershaderSetting(bool value) override { enableUbershader = value; }
|
||||
|
||||
std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
||||
|
||||
// Note: The caller is responsible for deleting the currently bound FBO before calling this
|
||||
void setFBO(uint handle) { screenFramebuffer.m_handle = handle; }
|
||||
void resetStateManager() { gl.reset(); }
|
||||
void clearShaderCache();
|
||||
void initUbershader(OpenGL::Program& program);
|
||||
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override { initGraphicsContextInternal(); }
|
||||
|
@ -95,4 +127,4 @@ class RendererGL final : public Renderer {
|
|||
|
||||
// Take a screenshot of the screen and store it in a file
|
||||
void screenshot(const std::string& name) override;
|
||||
};
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue