mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-15 10:40:01 +01:00
Merge branch 'master' into TemporaryHackishBranch
# Conflicts: # GPU/Common/ShaderCommon.h # GPU/Common/ShaderUniforms.h # GPU/Directx9/ShaderManagerDX9.cpp # GPU/Directx9/VertexShaderGeneratorDX9.cpp # GPU/Vulkan/ShaderManagerVulkan.cpp # UI/GameSettingsScreen.cpp # headless/Headless.cpp
This commit is contained in:
commit
63cf6d283f
78 files changed
+2634
-491
No files matched your search
+3
-1
@@ -722,10 +722,12 @@ elseif(IOS)
|
||||
ios/PPSSPPUIApplication.mm
|
||||
ios/SmartKeyboardMap.cpp
|
||||
ios/SmartKeyboardMap.hpp
|
||||
ios/SubtleVolume.h
|
||||
ios/SubtleVolume.mm
|
||||
ios/iCade/iCadeReaderView.h
|
||||
ios/iCade/iCadeReaderView.m
|
||||
ios/iCade/iCadeState.h)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation")
|
||||
set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation")
|
||||
if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework")
|
||||
set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController")
|
||||
endif()
|
||||
|
||||
@@ -67,6 +67,8 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
|
||||
CheckSetting(iniFile, gameID, "RequireDefaultCPUClock", &flags_.RequireDefaultCPUClock);
|
||||
CheckSetting(iniFile, gameID, "DisableReadbacks", &flags_.DisableReadbacks);
|
||||
CheckSetting(iniFile, gameID, "DisableAccurateDepth", &flags_.DisableAccurateDepth);
|
||||
CheckSetting(iniFile, gameID, "MGS2AcidHack", &flags_.MGS2AcidHack);
|
||||
CheckSetting(iniFile, gameID, "SonicRivalsHack", &flags_.SonicRivalsHack);
|
||||
}
|
||||
|
||||
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) {
|
||||
|
||||
@@ -67,6 +67,8 @@ struct CompatFlags {
|
||||
bool Force04Download;
|
||||
bool GuardBand;
|
||||
bool DisableAccurateDepth;
|
||||
bool MGS2AcidHack;
|
||||
bool SonicRivalsHack;
|
||||
};
|
||||
|
||||
class IniFile;
|
||||
|
||||
@@ -515,6 +515,7 @@ static ConfigSetting graphicsSettings[] = {
|
||||
ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, &DefaultRenderingMode, true, true),
|
||||
ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true),
|
||||
ReportedConfigSetting("HardwareTransform", &g_Config.bHardwareTransform, true, true, true),
|
||||
ReportedConfigSetting("SoftwareSkinning", &g_Config.bSoftwareSkinning, true, true, true),
|
||||
ReportedConfigSetting("TextureFiltering", &g_Config.iTexFiltering, 1, true, true),
|
||||
ReportedConfigSetting("BufferFiltering", &g_Config.iBufFilter, 1, true, true),
|
||||
ReportedConfigSetting("InternalResolution", &g_Config.iInternalResolution, &DefaultInternalResolution, true, true),
|
||||
|
||||
@@ -161,6 +161,7 @@ public:
|
||||
int iGPUBackend;
|
||||
bool bSoftwareRendering;
|
||||
bool bHardwareTransform; // only used in the GLES backend
|
||||
bool bSoftwareSkinning; // may speed up some games
|
||||
|
||||
int iRenderingMode; // 0 = non-buffered rendering 1 = buffered rendering
|
||||
int iTexFiltering; // 1 = off , 2 = nearest , 3 = linear , 4 = linear(CG)
|
||||
|
||||
@@ -37,7 +37,7 @@ LocalFileLoader::LocalFileLoader(const std::string &filename)
|
||||
if (fd_ == -1) {
|
||||
return;
|
||||
}
|
||||
#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
|
||||
#if PPSSPP_PLATFORM(ANDROID) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
|
||||
off64_t off = lseek64(fd_, 0, SEEK_END);
|
||||
filesize_ = off;
|
||||
lseek64(fd_, 0, SEEK_SET);
|
||||
@@ -112,7 +112,21 @@ std::string LocalFileLoader::Path() const {
|
||||
}
|
||||
|
||||
size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags) {
|
||||
#ifndef _WIN32
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
// pread64 doesn't appear to actually be 64-bit safe, though such ISOs are uncommon. See #10862.
|
||||
if (absolutePos <= 0x7FFFFFFF) {
|
||||
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64
|
||||
return pread64(fd_, data, bytes * count, absolutePos) / bytes;
|
||||
#else
|
||||
return pread(fd_, data, bytes * count, absolutePos) / bytes;
|
||||
#endif
|
||||
} else {
|
||||
// Since pread64 doesn't change the file offset, it should be safe to avoid the lock in the common case.
|
||||
std::lock_guard<std::mutex> guard(readLock_);
|
||||
lseek64(fd_, absolutePos, SEEK_SET);
|
||||
return read(fd_, data, bytes * count) / bytes;
|
||||
}
|
||||
#elif !defined(_WIN32)
|
||||
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64
|
||||
return pread64(fd_, data, bytes * count, absolutePos) / bytes;
|
||||
#else
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/Loaders.h"
|
||||
#ifdef _WIN32
|
||||
@@ -42,4 +43,5 @@ private:
|
||||
#endif
|
||||
u64 filesize_;
|
||||
std::string filename_;
|
||||
std::mutex readLock_;
|
||||
};
|
||||
@@ -156,12 +156,16 @@ static const IRMeta irMeta[] = {
|
||||
{ IROp::ExitToConstIfLtZ, "ExitIfLtZ", "CG", IRFLAG_EXIT },
|
||||
{ IROp::ExitToReg, "ExitToReg", "_G", IRFLAG_EXIT },
|
||||
{ IROp::Syscall, "Syscall", "_C", IRFLAG_EXIT },
|
||||
{ IROp::Break, "Break", "", IRFLAG_EXIT},
|
||||
{ IROp::Break, "Break", "", IRFLAG_EXIT },
|
||||
{ IROp::SetPC, "SetPC", "_G" },
|
||||
{ IROp::SetPCConst, "SetPC", "_C" },
|
||||
{ IROp::CallReplacement, "CallRepl", "_C" },
|
||||
{ IROp::Breakpoint, "Breakpoint", "", IRFLAG_EXIT },
|
||||
{ IROp::MemoryCheck, "MemoryCheck", "_GC", IRFLAG_EXIT },
|
||||
|
||||
{ IROp::RestoreRoundingMode, "RestoreRoundingMode", "" },
|
||||
{ IROp::ApplyRoundingMode, "ApplyRoundingMode", "" },
|
||||
{ IROp::UpdateRoundingMode, "UpdateRoundingMode", "" },
|
||||
};
|
||||
|
||||
const IRMeta *metaIndex[256];
|
||||
|
||||
@@ -977,7 +977,18 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ApplyRoundingMode:
|
||||
// TODO: Implement
|
||||
break;
|
||||
case IROp::RestoreRoundingMode:
|
||||
// TODO: Implement
|
||||
break;
|
||||
case IROp::UpdateRoundingMode:
|
||||
// TODO: Implement
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unimplemented IR op. Bad.
|
||||
Crash();
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
|
||||
@@ -878,7 +878,7 @@ namespace MIPSAnalyst {
|
||||
|
||||
for (auto iter = functions.begin(), end = functions.end(); iter != end; iter++) {
|
||||
AnalyzedFunction &f = *iter;
|
||||
if (Memory::IsValidRange(f.start, f.end - f.start + 4)) {
|
||||
if (!Memory::IsValidRange(f.start, f.end - f.start + 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+3
-13
@@ -375,19 +375,9 @@ namespace MIPSInt
|
||||
{
|
||||
case 10: if (R(rt) == 0) R(rd) = R(rs); break; //movz
|
||||
case 11: if (R(rt) != 0) R(rd) = R(rs); break; //movn
|
||||
case 32:
|
||||
if (!has_warned) {
|
||||
ERROR_LOG(CPU,"WARNING : exception-causing add at %08x", PC);
|
||||
has_warned = true;
|
||||
}
|
||||
R(rd) = R(rs) + R(rt); break; //add
|
||||
case 32: R(rd) = R(rs) + R(rt); break; //add (exception on overflow)
|
||||
case 33: R(rd) = R(rs) + R(rt); break; //addu
|
||||
case 34:
|
||||
if (!has_warned) {
|
||||
ERROR_LOG(CPU,"WARNING : exception-causing sub at %08x", PC);
|
||||
has_warned = true;
|
||||
}
|
||||
R(rd) = R(rs) - R(rt); break; //sub
|
||||
case 34: R(rd) = R(rs) - R(rt); break; //sub (exception on overflow)
|
||||
case 35: R(rd) = R(rs) - R(rt); break; //subu
|
||||
case 36: R(rd) = R(rs) & R(rt); break; //and
|
||||
case 37: R(rd) = R(rs) | R(rt); break; //or
|
||||
@@ -398,7 +388,7 @@ namespace MIPSInt
|
||||
case 44: R(rd) = ((s32)R(rs) > (s32)R(rt)) ? R(rs) : R(rt); break; //max
|
||||
case 45: R(rd) = ((s32)R(rs) < (s32)R(rt)) ? R(rs) : R(rt); break;//min
|
||||
default:
|
||||
_dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted");
|
||||
_dbg_assert_msg_(CPU, 0, "Unknown MIPS instruction %08x", op.encoding);
|
||||
break;
|
||||
}
|
||||
PC += 4;
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@ static void ReplayExecuteCtrl(uint32_t &buttons, uint8_t analog[2][2], uint64_t
|
||||
|
||||
// We have to always apply the latest state here, because otherwise real input is used between changes.
|
||||
buttons = lastButtons;
|
||||
memcpy(analog, lastAnalog, sizeof(analog));
|
||||
memcpy(analog, lastAnalog, sizeof(lastAnalog));
|
||||
|
||||
if (replayExecPos < replayCtrlPos) {
|
||||
replayExecPos = replayCtrlPos;
|
||||
|
||||
@@ -70,7 +70,7 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang
|
||||
int mask = gstate.getClutIndexMask();
|
||||
int shift = gstate.getClutIndexShift();
|
||||
int offset = gstate.getClutIndexStartPos();
|
||||
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
|
||||
GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
|
||||
// Unfortunately sampling turned our texture into floating point. To avoid this, might be able
|
||||
// to declare them as isampler2D objects, but these require integer textures, which needs more work.
|
||||
// Anyhow, we simply work around this by converting back to integer. Hopefully there will be no loss of precision.
|
||||
|
||||
@@ -379,9 +379,13 @@ bool DrawEngineCommon::GetCurrentSimpleVertices(int count, std::vector<GPUDebugV
|
||||
// The implementation is initially a bit inefficient but shouldn't be a big deal.
|
||||
// An intermediate buffer of not-easy-to-predict size is stored at bufPtr.
|
||||
u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, VertexDecoder *dec, int lowerBound, int upperBound, u32 vertType) {
|
||||
// First, decode the vertices into a GPU compatible format.
|
||||
// First, decode the vertices into a GPU compatible format. This step can be eliminated but will need a separate
|
||||
// implementation of the vertex decoder.
|
||||
dec->DecodeVerts(bufPtr, inPtr, lowerBound, upperBound);
|
||||
|
||||
// OK, morphing eliminated but bones still remain to be taken care of.
|
||||
// Let's do a partial software transform where we only do skinning.
|
||||
|
||||
VertexReader reader(bufPtr, dec->GetDecVtxFmt(), vertType);
|
||||
|
||||
SimpleVertex *sverts = (SimpleVertex *)outPtr;
|
||||
@@ -393,7 +397,56 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr,
|
||||
(u8)gstate.getMaterialAmbientA(),
|
||||
};
|
||||
|
||||
{
|
||||
// Let's have two separate loops, one for non skinning and one for skinning.
|
||||
if (!g_Config.bSoftwareSkinning && (vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) {
|
||||
int numBoneWeights = vertTypeGetNumBoneWeights(vertType);
|
||||
for (int i = lowerBound; i <= upperBound; i++) {
|
||||
reader.Goto(i - lowerBound);
|
||||
SimpleVertex &sv = sverts[i];
|
||||
if (vertType & GE_VTYPE_TC_MASK) {
|
||||
reader.ReadUV(sv.uv);
|
||||
}
|
||||
|
||||
if (vertType & GE_VTYPE_COL_MASK) {
|
||||
reader.ReadColor0_8888(sv.color);
|
||||
} else {
|
||||
memcpy(sv.color, defaultColor, 4);
|
||||
}
|
||||
|
||||
float nrm[3], pos[3];
|
||||
float bnrm[3], bpos[3];
|
||||
|
||||
if (vertType & GE_VTYPE_NRM_MASK) {
|
||||
// Normals are generated during tessellation anyway, not sure if any need to supply
|
||||
reader.ReadNrm(nrm);
|
||||
} else {
|
||||
nrm[0] = 0;
|
||||
nrm[1] = 0;
|
||||
nrm[2] = 1.0f;
|
||||
}
|
||||
reader.ReadPos(pos);
|
||||
|
||||
// Apply skinning transform directly
|
||||
float weights[8];
|
||||
reader.ReadWeights(weights);
|
||||
// Skinning
|
||||
Vec3Packedf psum(0, 0, 0);
|
||||
Vec3Packedf nsum(0, 0, 0);
|
||||
for (int w = 0; w < numBoneWeights; w++) {
|
||||
if (weights[w] != 0.0f) {
|
||||
Vec3ByMatrix43(bpos, pos, gstate.boneMatrix + w * 12);
|
||||
Vec3Packedf tpos(bpos);
|
||||
psum += tpos * weights[w];
|
||||
|
||||
Norm3ByMatrix43(bnrm, nrm, gstate.boneMatrix + w * 12);
|
||||
Vec3Packedf tnorm(bnrm);
|
||||
nsum += tnorm * weights[w];
|
||||
}
|
||||
}
|
||||
sv.pos = psum;
|
||||
sv.nrm = nsum;
|
||||
}
|
||||
} else {
|
||||
for (int i = lowerBound; i <= upperBound; i++) {
|
||||
reader.Goto(i - lowerBound);
|
||||
SimpleVertex &sv = sverts[i];
|
||||
@@ -655,7 +708,7 @@ void DrawEngineCommon::SubmitPrim(void *verts, void *inds, GEPrimitiveType prim,
|
||||
numDrawCalls++;
|
||||
vertexCountInDrawCalls_ += vertexCount;
|
||||
|
||||
if (vertTypeID & GE_VTYPE_WEIGHT_MASK) {
|
||||
if (g_Config.bSoftwareSkinning && (vertTypeID & GE_VTYPE_WEIGHT_MASK)) {
|
||||
DecodeVertsStep(decoded, decodeCounter_, decodedVerts_);
|
||||
decodeCounter_++;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ protected:
|
||||
TransformedVertex *transformed = nullptr;
|
||||
TransformedVertex *transformedExpanded = nullptr;
|
||||
|
||||
// Defer all vertex decoding to a "Flush" (except when skinning)
|
||||
// Defer all vertex decoding to a "Flush" (except when software skinning)
|
||||
struct DeferredDrawCall {
|
||||
void *verts;
|
||||
void *inds;
|
||||
|
||||
@@ -78,15 +78,25 @@ enum : uint64_t {
|
||||
DIRTY_WORLDMATRIX = 1ULL << 21,
|
||||
DIRTY_VIEWMATRIX = 1ULL << 22,
|
||||
DIRTY_TEXMATRIX = 1ULL << 23,
|
||||
DIRTY_BONEMATRIX0 = 1ULL << 24, // NOTE: These must be under 32
|
||||
DIRTY_BONEMATRIX1 = 1ULL << 25,
|
||||
DIRTY_BONEMATRIX2 = 1ULL << 26,
|
||||
DIRTY_BONEMATRIX3 = 1ULL << 27,
|
||||
DIRTY_BONEMATRIX4 = 1ULL << 28,
|
||||
DIRTY_BONEMATRIX5 = 1ULL << 29,
|
||||
DIRTY_BONEMATRIX6 = 1ULL << 30,
|
||||
DIRTY_BONEMATRIX7 = 1ULL << 31,
|
||||
|
||||
// 8 free bits here where bones used to be!
|
||||
|
||||
// These are for hardware tessellation
|
||||
DIRTY_BEZIERSPLINE = 1ULL << 32,
|
||||
DIRTY_TEXCLAMP = 1ULL << 33,
|
||||
DIRTY_TEXSIZE = 1ULL << 34,
|
||||
|
||||
// space for 5 more uniforms.
|
||||
DIRTY_DEPAL = 1ULL << 35,
|
||||
|
||||
// space for 4 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS.
|
||||
|
||||
DIRTY_BONE_UNIFORMS = 0xFF000000ULL,
|
||||
|
||||
DIRTY_ALL_UNIFORMS = 0x7FFFFFFFFULL,
|
||||
DIRTY_ALL_LIGHTS = DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3,
|
||||
|
||||
@@ -33,6 +33,7 @@ std::string VertexShaderDesc(const ShaderID &id) {
|
||||
int ls1 = id.Bits(VS_BIT_LS1, 2);
|
||||
|
||||
if (uvgMode) desc << uvgModes[uvgMode];
|
||||
if (id.Bit(VS_BIT_ENABLE_BONES)) desc << "Bones:" << (id.Bits(VS_BIT_BONES, 3) + 1) << " ";
|
||||
// Lights
|
||||
if (id.Bit(VS_BIT_LIGHTING_ENABLE)) {
|
||||
desc << "Light: ";
|
||||
@@ -45,6 +46,7 @@ std::string VertexShaderDesc(const ShaderID &id) {
|
||||
}
|
||||
}
|
||||
if (id.Bits(VS_BIT_MATERIAL_UPDATE, 3)) desc << "MatUp:" << id.Bits(VS_BIT_MATERIAL_UPDATE, 3) << " ";
|
||||
if (id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2)) desc << "WScale " << id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2) << " ";
|
||||
if (id.Bit(VS_BIT_FLATSHADE)) desc << "Flat ";
|
||||
|
||||
if (id.Bit(VS_BIT_BEZIER)) desc << "Bezier ";
|
||||
@@ -101,6 +103,16 @@ void ComputeVertexShaderID(ShaderID *id_out, u32 vertType, bool useHWTransform)
|
||||
id.SetBits(VS_BIT_LS1, 2, gstate.getUVLS1());
|
||||
}
|
||||
|
||||
// Bones.
|
||||
bool enableBones = vertTypeIsSkinningEnabled(vertType);
|
||||
id.SetBit(VS_BIT_ENABLE_BONES, enableBones);
|
||||
if (enableBones) {
|
||||
id.SetBits(VS_BIT_BONES, 3, TranslateNumBones(vertTypeGetNumBoneWeights(vertType)) - 1);
|
||||
// 2 bits. We should probably send in the weight scalefactor as a uniform instead,
|
||||
// or simply preconvert all weights to floats.
|
||||
id.SetBits(VS_BIT_WEIGHT_FMTSCALE, 2, (vertType & GE_VTYPE_WEIGHT_MASK) >> GE_VTYPE_WEIGHT_SHIFT);
|
||||
}
|
||||
|
||||
// Okay, d[1] coming up. ==============
|
||||
if (gstate.isLightingEnabled() || doShadeMapping) {
|
||||
// doShadeMapping is stored as UVGenMode, so this is enough for isLightingEnabled.
|
||||
@@ -159,6 +171,7 @@ std::string FragmentShaderDesc(const ShaderID &id) {
|
||||
if (id.Bit(FS_BIT_COLOR_DOUBLE)) desc << "2x ";
|
||||
if (id.Bit(FS_BIT_FLATSHADE)) desc << "Flat ";
|
||||
if (id.Bit(FS_BIT_BGRA_TEXTURE)) desc << "BGRA ";
|
||||
if (id.Bit(FS_BIT_SHADER_DEPAL)) desc << "Depal ";
|
||||
if (id.Bit(FS_BIT_SHADER_TEX_CLAMP)) {
|
||||
desc << "TClamp";
|
||||
if (id.Bit(FS_BIT_CLAMP_S)) desc << "S";
|
||||
@@ -224,6 +237,7 @@ void ComputeFragmentShaderID(ShaderID *id_out) {
|
||||
bool doTextureProjection = (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX && MatrixNeedsProjection(gstate.tgenMatrix));
|
||||
bool doTextureAlpha = gstate.isTextureAlphaUsed();
|
||||
bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT;
|
||||
bool useShaderDepal = gstate_c.useShaderDepal;
|
||||
|
||||
ReplaceBlendType replaceBlend = ReplaceBlendWithShader(gstate_c.allowShaderBlend, gstate.FrameBufFormat());
|
||||
ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(replaceBlend);
|
||||
@@ -287,6 +301,8 @@ void ComputeFragmentShaderID(ShaderID *id_out) {
|
||||
id.SetBits(FS_BIT_BLENDFUNC_B, 4, gstate.getBlendFuncB());
|
||||
}
|
||||
id.SetBit(FS_BIT_FLATSHADE, doFlatShading);
|
||||
|
||||
id.SetBit(FS_BIT_SHADER_DEPAL, useShaderDepal);
|
||||
}
|
||||
|
||||
*id_out = id;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
// TODO: There will be additional bits, indicating that groups of these will be
|
||||
// sent to the shader and processed there. This will cut down the number of shaders ("ubershader approach")
|
||||
// This is probably only really worth doing for lighting and bones.
|
||||
enum {
|
||||
VS_BIT_LMODE = 0,
|
||||
VS_BIT_IS_THROUGH = 1,
|
||||
@@ -28,7 +29,10 @@ enum {
|
||||
VS_BIT_UVPROJ_MODE = 18, // 2, can overlap with LS0
|
||||
VS_BIT_LS0 = 18, // 2
|
||||
VS_BIT_LS1 = 20, // 2
|
||||
// 22 - 31 are free.
|
||||
VS_BIT_BONES = 22, // 3 should be enough, not 8
|
||||
// 25 - 29 are free.
|
||||
VS_BIT_ENABLE_BONES = 30,
|
||||
// 31 is free.
|
||||
VS_BIT_LIGHT0_COMP = 32, // 2 bits
|
||||
VS_BIT_LIGHT0_TYPE = 34, // 2 bits
|
||||
VS_BIT_LIGHT1_COMP = 36, // 2 bits
|
||||
@@ -44,7 +48,8 @@ enum {
|
||||
VS_BIT_LIGHT2_ENABLE = 54,
|
||||
VS_BIT_LIGHT3_ENABLE = 55,
|
||||
VS_BIT_LIGHTING_ENABLE = 56,
|
||||
// 57 - 61 are free.
|
||||
VS_BIT_WEIGHT_FMTSCALE = 57, // only two bits
|
||||
// 59 - 61 are free.
|
||||
VS_BIT_FLATSHADE = 62, // 1 bit
|
||||
VS_BIT_BEZIER = 63, // 1 bit
|
||||
// No more free
|
||||
@@ -57,7 +62,7 @@ enum {
|
||||
FS_BIT_DO_TEXTURE = 1,
|
||||
FS_BIT_TEXFUNC = 2, // 3 bits
|
||||
FS_BIT_TEXALPHA = 5,
|
||||
// 6 is free.
|
||||
FS_BIT_SHADER_DEPAL = 6,
|
||||
FS_BIT_SHADER_TEX_CLAMP = 7,
|
||||
FS_BIT_CLAMP_S = 8,
|
||||
FS_BIT_CLAMP_T = 9,
|
||||
|
||||
@@ -222,10 +222,18 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_BEZIERSPLINE) {
|
||||
ub->spline_count_u = gstate_c.spline_count_u;
|
||||
ub->spline_count_v = gstate_c.spline_count_v;
|
||||
ub->spline_type_u = gstate_c.spline_type_u;
|
||||
ub->spline_type_v = gstate_c.spline_type_v;
|
||||
ub->spline_counts = BytesToUint32(gstate_c.spline_count_u, gstate_c.spline_count_v, gstate_c.spline_type_u, gstate_c.spline_type_v);
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_DEPAL) {
|
||||
int indexMask = gstate.getClutIndexMask();
|
||||
int indexShift = gstate.getClutIndexShift();
|
||||
int indexOffset = gstate.getClutIndexStartPos() >> 4;
|
||||
int format = gstate_c.depalFramebufferFormat;
|
||||
uint32_t val = BytesToUint32(indexMask, indexShift, indexOffset, format);
|
||||
// Poke in a bilinear filter flag in the top bit.
|
||||
val |= gstate.isMagnifyFilteringEnabled() << 31;
|
||||
ub->depal_mask_shift_off_fmt = val;
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_TEXSIZE) {
|
||||
@@ -278,3 +286,11 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoneUpdateUniforms(UB_VS_Bones *ub, uint64_t dirtyUniforms) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (dirtyUniforms & (DIRTY_BONEMATRIX0 << i)) {
|
||||
ConvertMatrix4x3To3x4Transposed(ub->bones[i], gstate.boneMatrix + 12 * i);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-10
@@ -11,7 +11,7 @@ enum : uint64_t {
|
||||
DIRTY_WORLDMATRIX | DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWMATRIX | DIRTY_TEXMATRIX | DIRTY_ALPHACOLORREF |
|
||||
DIRTY_PROJMATRIX | DIRTY_FOGCOLOR | DIRTY_FOGCOEF | DIRTY_TEXENV | DIRTY_STENCILREPLACEVALUE | DIRTY_GUARDBAND |
|
||||
DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA |
|
||||
DIRTY_BEZIERSPLINE | DIRTY_TEXSIZE,
|
||||
DIRTY_BEZIERSPLINE | DIRTY_TEXSIZE | DIRTY_DEPAL,
|
||||
DIRTY_LIGHT_UNIFORMS =
|
||||
DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 |
|
||||
DIRTY_MATDIFFUSE | DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE | DIRTY_AMBIENT,
|
||||
@@ -30,7 +30,8 @@ struct UB_VS_FS_Base {
|
||||
float depthRange[4];
|
||||
float fogCoef[2]; float stencil; float pad0;
|
||||
float matAmbient[4];
|
||||
int spline_count_u; int spline_count_v; int spline_type_u; int spline_type_v;
|
||||
uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one.
|
||||
int pad2; int pad3;
|
||||
float guardband[4];
|
||||
// Fragment data
|
||||
float fogColor[4];
|
||||
@@ -55,10 +56,10 @@ R"( mat4 proj_mtx;
|
||||
vec2 fogcoef;
|
||||
float stencilReplace;
|
||||
vec4 matambientalpha;
|
||||
int spline_count_u;
|
||||
int spline_count_v;
|
||||
int spline_type_u;
|
||||
int spline_type_v;
|
||||
uint spline_counts;
|
||||
uint depal_mask_shift_off_fmt;
|
||||
int pad2;
|
||||
int pad3;
|
||||
vec4 guardband;
|
||||
// Fragment
|
||||
vec3 fogcolor;
|
||||
@@ -84,10 +85,10 @@ R"( float4x4 u_proj;
|
||||
float2 u_fogcoef;
|
||||
float u_stencilReplaceValue;
|
||||
float4 u_matambientalpha;
|
||||
int u_spline_count_u;
|
||||
int u_spline_count_v;
|
||||
int u_spline_type_u;
|
||||
int u_spline_type_v;
|
||||
uint u_spline_counts;
|
||||
uint u_depal_mask_shift_off_fmt;
|
||||
int pad2;
|
||||
int pad3;
|
||||
float4 u_guardband;
|
||||
// Fragment
|
||||
float3 u_fogcolor;
|
||||
@@ -167,8 +168,24 @@ R"( float4 u_ambient;
|
||||
float3 u_lightspecular3;
|
||||
)";
|
||||
|
||||
// With some cleverness, we could get away with uploading just half this when only the four or five first
|
||||
// bones are being used. This is 512b, 256b would be great.
|
||||
struct UB_VS_Bones {
|
||||
float bones[8][12];
|
||||
};
|
||||
|
||||
static const char *ub_vs_bonesStr =
|
||||
R"( mat3x4 m[8];
|
||||
)";
|
||||
|
||||
// HLSL code is shared so these names are changed to match those in DX9.
|
||||
static const char *cb_vs_bonesStr =
|
||||
R"( float4x3 u_bone[8];
|
||||
)";
|
||||
|
||||
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport);
|
||||
void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms);
|
||||
void BoneUpdateUniforms(UB_VS_Bones *ub, uint64_t dirtyUniforms);
|
||||
|
||||
// Shared helper functions
|
||||
void ComputeGuardband(float gb[4], float zmin);
|
||||
@@ -144,6 +144,8 @@ void SoftwareTransform(
|
||||
vscale /= gstate_c.curTextureHeight;
|
||||
}
|
||||
|
||||
bool skinningEnabled = vertTypeIsSkinningEnabled(vertType);
|
||||
|
||||
const int w = gstate.getTextureWidth(0);
|
||||
const int h = gstate.getTextureHeight(0);
|
||||
float widthFactor = (float) w / (float) gstate_c.curTextureWidth;
|
||||
@@ -211,14 +213,48 @@ void SoftwareTransform(
|
||||
Vec3f worldnormal(0, 0, 1);
|
||||
reader.ReadPos(pos);
|
||||
|
||||
Vec3ByMatrix43(out, pos, gstate.worldMatrix);
|
||||
if (reader.hasNormal()) {
|
||||
reader.ReadNrm(normal.AsArray());
|
||||
if (gstate.areNormalsReversed()) {
|
||||
normal = -normal;
|
||||
if (!skinningEnabled) {
|
||||
Vec3ByMatrix43(out, pos, gstate.worldMatrix);
|
||||
if (reader.hasNormal()) {
|
||||
reader.ReadNrm(normal.AsArray());
|
||||
if (gstate.areNormalsReversed()) {
|
||||
normal = -normal;
|
||||
}
|
||||
Norm3ByMatrix43(worldnormal.AsArray(), normal.AsArray(), gstate.worldMatrix);
|
||||
worldnormal = worldnormal.Normalized();
|
||||
}
|
||||
} else {
|
||||
float weights[8];
|
||||
reader.ReadWeights(weights);
|
||||
if (reader.hasNormal())
|
||||
reader.ReadNrm(normal.AsArray());
|
||||
|
||||
// Skinning
|
||||
Vec3f psum(0, 0, 0);
|
||||
Vec3f nsum(0, 0, 0);
|
||||
for (int i = 0; i < vertTypeGetNumBoneWeights(vertType); i++) {
|
||||
if (weights[i] != 0.0f) {
|
||||
Vec3ByMatrix43(out, pos, gstate.boneMatrix+i*12);
|
||||
Vec3f tpos(out);
|
||||
psum += tpos * weights[i];
|
||||
if (reader.hasNormal()) {
|
||||
Vec3f norm;
|
||||
Norm3ByMatrix43(norm.AsArray(), normal.AsArray(), gstate.boneMatrix+i*12);
|
||||
nsum += norm * weights[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, we really must multiply by the world matrix too.
|
||||
Vec3ByMatrix43(out, psum.AsArray(), gstate.worldMatrix);
|
||||
if (reader.hasNormal()) {
|
||||
normal = nsum;
|
||||
if (gstate.areNormalsReversed()) {
|
||||
normal = -normal;
|
||||
}
|
||||
Norm3ByMatrix43(worldnormal.AsArray(), normal.AsArray(), gstate.worldMatrix);
|
||||
worldnormal = worldnormal.Normalized();
|
||||
}
|
||||
Norm3ByMatrix43(worldnormal.AsArray(), normal.AsArray(), gstate.worldMatrix);
|
||||
worldnormal = worldnormal.Normalized();
|
||||
}
|
||||
|
||||
// Perform lighting here if enabled. don't need to check through, it's checked above.
|
||||
|
||||
@@ -111,6 +111,9 @@ static const ARMReg srcNEON = Q2;
|
||||
static const ARMReg accNEON = Q3;
|
||||
|
||||
static const JitLookup jitLookup[] = {
|
||||
{&VertexDecoder::Step_WeightsU8, &VertexDecoderJitCache::Jit_WeightsU8},
|
||||
{&VertexDecoder::Step_WeightsU16, &VertexDecoderJitCache::Jit_WeightsU16},
|
||||
{&VertexDecoder::Step_WeightsFloat, &VertexDecoderJitCache::Jit_WeightsFloat},
|
||||
{&VertexDecoder::Step_WeightsU8Skin, &VertexDecoderJitCache::Jit_WeightsU8Skin},
|
||||
{&VertexDecoder::Step_WeightsU16Skin, &VertexDecoderJitCache::Jit_WeightsU16Skin},
|
||||
{&VertexDecoder::Step_WeightsFloatSkin, &VertexDecoderJitCache::Jit_WeightsFloatSkin},
|
||||
@@ -229,7 +232,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
|
||||
// Add code to convert matrices to 4x4.
|
||||
// Later we might want to do this when the matrices are loaded instead.
|
||||
int boneCount = 0;
|
||||
if (NEONSkinning && dec.weighttype) {
|
||||
if (NEONSkinning && dec.weighttype && g_Config.bSoftwareSkinning) {
|
||||
// Copying from R3 to R4
|
||||
MOVP2R(R3, gstate.boneMatrix);
|
||||
MOVP2R(R4, bones);
|
||||
@@ -323,6 +326,55 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
|
||||
return (JittedVertexDecoder)start;
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8() {
|
||||
// Basic implementation - a byte at a time. TODO: Optimize
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDRB(tempReg1, srcReg, dec_->weightoff + j);
|
||||
STRB(tempReg1, dstReg, dec_->decFmt.w0off + j);
|
||||
}
|
||||
if (j & 3) {
|
||||
// Create a zero register. Might want to make a fixed one.
|
||||
EOR(scratchReg, scratchReg, scratchReg);
|
||||
}
|
||||
while (j & 3) {
|
||||
STRB(scratchReg, dstReg, dec_->decFmt.w0off + j);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU16() {
|
||||
// Basic implementation - a short at a time. TODO: Optimize
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDRH(tempReg1, srcReg, dec_->weightoff + j * 2);
|
||||
STRH(tempReg1, dstReg, dec_->decFmt.w0off + j * 2);
|
||||
}
|
||||
if (j & 3) {
|
||||
// Create a zero register. Might want to make a fixed one.
|
||||
EOR(scratchReg, scratchReg, scratchReg);
|
||||
}
|
||||
while (j & 3) {
|
||||
STRH(scratchReg, dstReg, dec_->decFmt.w0off + j * 2);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsFloat() {
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDR(tempReg1, srcReg, dec_->weightoff + j * 4);
|
||||
STR(tempReg1, dstReg, dec_->decFmt.w0off + j * 4);
|
||||
}
|
||||
if (j & 3) {
|
||||
EOR(tempReg1, tempReg1, tempReg1);
|
||||
}
|
||||
while (j & 3) { // Zero additional weights rounding up to 4.
|
||||
STR(tempReg1, dstReg, dec_->decFmt.w0off + j * 4);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
static const ARMReg weightRegs[8] = { S8, S9, S10, S11, S12, S13, S14, S15 };
|
||||
static const ARMReg neonWeightRegsD[4] = { D4, D5, D6, D7 };
|
||||
static const ARMReg neonWeightRegsQ[2] = { Q2, Q3 };
|
||||
|
||||
@@ -85,6 +85,9 @@ static const ARM64Reg neonWeightRegsQ[2] = { Q3, Q2 }; // reverse order to prev
|
||||
// Q16+ are free-for-all for matrices. In 16 registers, we can fit 4 4x4 matrices.
|
||||
|
||||
static const JitLookup jitLookup[] = {
|
||||
{&VertexDecoder::Step_WeightsU8, &VertexDecoderJitCache::Jit_WeightsU8},
|
||||
{&VertexDecoder::Step_WeightsU16, &VertexDecoderJitCache::Jit_WeightsU16},
|
||||
{&VertexDecoder::Step_WeightsFloat, &VertexDecoderJitCache::Jit_WeightsFloat},
|
||||
{&VertexDecoder::Step_WeightsU8Skin, &VertexDecoderJitCache::Jit_WeightsU8Skin},
|
||||
{&VertexDecoder::Step_WeightsU16Skin, &VertexDecoderJitCache::Jit_WeightsU16Skin},
|
||||
{&VertexDecoder::Step_WeightsFloatSkin, &VertexDecoderJitCache::Jit_WeightsFloatSkin},
|
||||
@@ -193,7 +196,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
|
||||
// Add code to convert matrices to 4x4.
|
||||
// Later we might want to do this when the matrices are loaded instead.
|
||||
int boneCount = 0;
|
||||
if (dec.weighttype) {
|
||||
if (dec.weighttype && g_Config.bSoftwareSkinning) {
|
||||
// Copying from R3 to R4
|
||||
MOVP2R(X3, gstate.boneMatrix);
|
||||
MOVP2R(X4, bones);
|
||||
@@ -353,6 +356,44 @@ void VertexDecoderJitCache::Jit_ApplyWeights() {
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8() {
|
||||
// Basic implementation - a byte at a time. TODO: Optimize
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDRB(INDEX_UNSIGNED, tempReg1, srcReg, dec_->weightoff + j);
|
||||
STRB(INDEX_UNSIGNED, tempReg1, dstReg, dec_->decFmt.w0off + j);
|
||||
}
|
||||
while (j & 3) {
|
||||
STRB(INDEX_UNSIGNED, WZR, dstReg, dec_->decFmt.w0off + j);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU16() {
|
||||
// Basic implementation - a short at a time. TODO: Optimize
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDRH(INDEX_UNSIGNED, tempReg1, srcReg, dec_->weightoff + j * 2);
|
||||
STRH(INDEX_UNSIGNED, tempReg1, dstReg, dec_->decFmt.w0off + j * 2);
|
||||
}
|
||||
while (j & 3) {
|
||||
STRH(INDEX_UNSIGNED, WZR, dstReg, dec_->decFmt.w0off + j * 2);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsFloat() {
|
||||
int j;
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
LDR(INDEX_UNSIGNED, tempReg1, srcReg, dec_->weightoff + j * 4);
|
||||
STR(INDEX_UNSIGNED, tempReg1, dstReg, dec_->decFmt.w0off + j * 4);
|
||||
}
|
||||
while (j & 3) { // Zero additional weights rounding up to 4.
|
||||
STR(INDEX_UNSIGNED, WZR, dstReg, dec_->decFmt.w0off + j * 4);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8Skin() {
|
||||
// Weight is first so srcReg is correct.
|
||||
switch (dec_->nweights) {
|
||||
|
||||
@@ -41,7 +41,7 @@ static const u8 nrmsize[4] = { 0, 3, 6, 12 }, nrmalign[4] = { 0, 1, 2, 4 };
|
||||
static const u8 possize[4] = { 3, 3, 6, 12 }, posalign[4] = { 1, 1, 2, 4 };
|
||||
static const u8 wtsize[4] = { 0, 1, 2, 4 }, wtalign[4] = { 0, 1, 2, 4 };
|
||||
|
||||
// This array is only used when non-jitted - when jitted, the matrix
|
||||
// When software skinning. This array is only used when non-jitted - when jitted, the matrix
|
||||
// is kept in registers.
|
||||
alignas(16) static float skinMatrix[12];
|
||||
|
||||
@@ -49,6 +49,13 @@ inline int align(int n, int align) {
|
||||
return (n + (align - 1)) & ~(align - 1);
|
||||
}
|
||||
|
||||
int TranslateNumBones(int bones) {
|
||||
if (!bones) return 0;
|
||||
if (bones < 4) return 4;
|
||||
// if (bones < 8) return 8; I get drawing problems in FF:CC with this!
|
||||
return bones;
|
||||
}
|
||||
|
||||
int DecFmtSize(u8 fmt) {
|
||||
switch (fmt) {
|
||||
case DEC_NONE: return 0;
|
||||
@@ -72,17 +79,21 @@ int DecFmtSize(u8 fmt) {
|
||||
}
|
||||
|
||||
void DecVtxFormat::ComputeID() {
|
||||
id = uvfmt | (c0fmt << 4) | (c1fmt << 8) | (nrmfmt << 12) | (posfmt << 16);
|
||||
id = w0fmt | (w1fmt << 4) | (uvfmt << 8) | (c0fmt << 12) | (c1fmt << 16) | (nrmfmt << 20) | (posfmt << 24);
|
||||
}
|
||||
|
||||
void DecVtxFormat::InitializeFromID(uint32_t id) {
|
||||
this->id = id;
|
||||
uvfmt = (id & 0xF);
|
||||
c0fmt = ((id >> 4) & 0xF);
|
||||
c1fmt = ((id >> 8) & 0xF);
|
||||
nrmfmt = ((id >> 12) & 0xF);
|
||||
posfmt = ((id >> 16) & 0xF);
|
||||
uvoff = 0;
|
||||
w0fmt = ((id) & 0xF);
|
||||
w1fmt = ((id >> 4) & 0xF);
|
||||
uvfmt = ((id >> 8) & 0xF);
|
||||
c0fmt = ((id >> 12) & 0xF);
|
||||
c1fmt = ((id >> 16) & 0xF);
|
||||
nrmfmt = ((id >> 20) & 0xF);
|
||||
posfmt = ((id >> 24) & 0xF);
|
||||
w0off = 0;
|
||||
w1off = w0off + DecFmtSize(w0fmt);
|
||||
uvoff = w1off + DecFmtSize(w1fmt);
|
||||
c0off = uvoff + DecFmtSize(uvfmt);
|
||||
c1off = c0off + DecFmtSize(c0fmt);
|
||||
nrmoff = c1off + DecFmtSize(c1fmt);
|
||||
@@ -166,6 +177,67 @@ void PrintDecodedVertex(VertexReader &vtx) {
|
||||
VertexDecoder::VertexDecoder() : decoded_(nullptr), ptr_(nullptr), jitted_(0), jittedSize_(0) {
|
||||
}
|
||||
|
||||
void VertexDecoder::Step_WeightsU8() const
|
||||
{
|
||||
u8 *wt = (u8 *)(decoded_ + decFmt.w0off);
|
||||
const u8 *wdata = (const u8*)(ptr_);
|
||||
int j;
|
||||
for (j = 0; j < nweights; j++)
|
||||
wt[j] = wdata[j];
|
||||
while (j & 3) // Zero additional weights rounding up to 4.
|
||||
wt[j++] = 0;
|
||||
}
|
||||
|
||||
void VertexDecoder::Step_WeightsU16() const
|
||||
{
|
||||
u16 *wt = (u16 *)(decoded_ + decFmt.w0off);
|
||||
const u16 *wdata = (const u16*)(ptr_);
|
||||
int j;
|
||||
for (j = 0; j < nweights; j++)
|
||||
wt[j] = wdata[j];
|
||||
while (j & 3) // Zero additional weights rounding up to 4.
|
||||
wt[j++] = 0;
|
||||
}
|
||||
|
||||
void VertexDecoder::Step_WeightsU8ToFloat() const
|
||||
{
|
||||
float *wt = (float *)(decoded_ + decFmt.w0off);
|
||||
const u8 *wdata = (const u8*)(ptr_);
|
||||
int j;
|
||||
for (j = 0; j < nweights; j++) {
|
||||
wt[j] = (float)wdata[j] * (1.0f / 128.0f);
|
||||
}
|
||||
while (j & 3) // Zero additional weights rounding up to 4.
|
||||
wt[j++] = 0;
|
||||
}
|
||||
|
||||
void VertexDecoder::Step_WeightsU16ToFloat() const
|
||||
{
|
||||
float *wt = (float *)(decoded_ + decFmt.w0off);
|
||||
const u16 *wdata = (const u16*)(ptr_);
|
||||
int j;
|
||||
for (j = 0; j < nweights; j++) {
|
||||
wt[j] = (float)wdata[j] * (1.0f / 32768.0f);
|
||||
}
|
||||
while (j & 3) // Zero additional weights rounding up to 4.
|
||||
wt[j++] = 0;
|
||||
}
|
||||
|
||||
// Float weights should be uncommon, we can live with having to multiply these by 2.0
|
||||
// to avoid special checks in the vertex shader generator.
|
||||
// (PSP uses 0.0-2.0 fixed point numbers for weights)
|
||||
void VertexDecoder::Step_WeightsFloat() const
|
||||
{
|
||||
float *wt = (float *)(decoded_ + decFmt.w0off);
|
||||
const float *wdata = (const float*)(ptr_);
|
||||
int j;
|
||||
for (j = 0; j < nweights; j++) {
|
||||
wt[j] = wdata[j];
|
||||
}
|
||||
while (j & 3) // Zero additional weights rounding up to 4.
|
||||
wt[j++] = 0.0f;
|
||||
}
|
||||
|
||||
void VertexDecoder::ComputeSkinMatrix(const float weights[8]) const {
|
||||
memset(skinMatrix, 0, sizeof(skinMatrix));
|
||||
for (int j = 0; j < nweights; j++) {
|
||||
@@ -798,6 +870,20 @@ void VertexDecoder::Step_PosFloatMorphSkin() const {
|
||||
Vec3ByMatrix43(v, pos, skinMatrix);
|
||||
}
|
||||
|
||||
static const StepFunction wtstep[4] = {
|
||||
0,
|
||||
&VertexDecoder::Step_WeightsU8,
|
||||
&VertexDecoder::Step_WeightsU16,
|
||||
&VertexDecoder::Step_WeightsFloat,
|
||||
};
|
||||
|
||||
static const StepFunction wtstepToFloat[4] = {
|
||||
0,
|
||||
&VertexDecoder::Step_WeightsU8ToFloat,
|
||||
&VertexDecoder::Step_WeightsU16ToFloat,
|
||||
&VertexDecoder::Step_WeightsFloat,
|
||||
};
|
||||
|
||||
// TODO: Morph weights correctly! This is missing. Not sure if any game actually
|
||||
// use this functionality at all.
|
||||
|
||||
@@ -996,7 +1082,7 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
|
||||
DEBUG_LOG(G3D, "VTYPE: THRU=%i TC=%i COL=%i POS=%i NRM=%i WT=%i NW=%i IDX=%i MC=%i", (int)throughmode, tc, col, pos, nrm, weighttype, nweights, idx, morphcount);
|
||||
}
|
||||
|
||||
bool skinning = weighttype != 0;
|
||||
bool skinInDecode = weighttype != 0 && g_Config.bSoftwareSkinning;
|
||||
|
||||
if (weighttype) { // && nweights?
|
||||
weightoff = size;
|
||||
@@ -1005,11 +1091,43 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
|
||||
if (wtalign[weighttype] > biggest)
|
||||
biggest = wtalign[weighttype];
|
||||
|
||||
// No visible output, computes a matrix that is passed through the skinMatrix variable
|
||||
// to the "nrm" and "pos" steps.
|
||||
// Technically we should support morphing the weights too, but I have a hard time
|
||||
// imagining that any game would use that.. but you never know.
|
||||
steps_[numSteps_++] = wtstep_skin[weighttype];
|
||||
if (skinInDecode) {
|
||||
// No visible output, computes a matrix that is passed through the skinMatrix variable
|
||||
// to the "nrm" and "pos" steps.
|
||||
// Technically we should support morphing the weights too, but I have a hard time
|
||||
// imagining that any game would use that.. but you never know.
|
||||
steps_[numSteps_++] = wtstep_skin[weighttype];
|
||||
} else {
|
||||
int fmtBase = DEC_FLOAT_1;
|
||||
if (options.expandAllWeightsToFloat) {
|
||||
steps_[numSteps_++] = wtstepToFloat[weighttype];
|
||||
fmtBase = DEC_FLOAT_1;
|
||||
} else {
|
||||
steps_[numSteps_++] = wtstep[weighttype];
|
||||
if (weighttype == GE_VTYPE_WEIGHT_8BIT >> GE_VTYPE_WEIGHT_SHIFT) {
|
||||
fmtBase = DEC_U8_1;
|
||||
} else if (weighttype == GE_VTYPE_WEIGHT_16BIT >> GE_VTYPE_WEIGHT_SHIFT) {
|
||||
fmtBase = DEC_U16_1;
|
||||
} else if (weighttype == GE_VTYPE_WEIGHT_FLOAT >> GE_VTYPE_WEIGHT_SHIFT) {
|
||||
fmtBase = DEC_FLOAT_1;
|
||||
}
|
||||
}
|
||||
|
||||
int numWeights = TranslateNumBones(nweights);
|
||||
|
||||
if (numWeights <= 4) {
|
||||
decFmt.w0off = decOff;
|
||||
decFmt.w0fmt = fmtBase + numWeights - 1;
|
||||
decOff += DecFmtSize(decFmt.w0fmt);
|
||||
} else {
|
||||
decFmt.w0off = decOff;
|
||||
decFmt.w0fmt = fmtBase + 3;
|
||||
decOff += DecFmtSize(decFmt.w0fmt);
|
||||
decFmt.w1off = decOff;
|
||||
decFmt.w1fmt = fmtBase + numWeights - 5;
|
||||
decOff += DecFmtSize(decFmt.w1fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tc) {
|
||||
@@ -1067,7 +1185,7 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
|
||||
if (nrmalign[nrm] > biggest)
|
||||
biggest = nrmalign[nrm];
|
||||
|
||||
if (skinning) {
|
||||
if (skinInDecode) {
|
||||
steps_[numSteps_++] = morphcount == 1 ? nrmstep_skin[nrm] : nrmstep_morphskin[nrm];
|
||||
// After skinning, we always have three floats.
|
||||
decFmt.nrmfmt = DEC_FLOAT_3;
|
||||
@@ -1118,7 +1236,7 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
|
||||
steps_[numSteps_++] = posstep_through[pos];
|
||||
decFmt.posfmt = DEC_FLOAT_3;
|
||||
} else {
|
||||
if (skinning) {
|
||||
if (skinInDecode) {
|
||||
steps_[numSteps_++] = morphcount == 1 ? posstep_skin[pos] : posstep_morph_skin[pos];
|
||||
decFmt.posfmt = DEC_FLOAT_3;
|
||||
} else {
|
||||
|
||||
@@ -67,6 +67,8 @@ enum {
|
||||
int DecFmtSize(u8 fmt);
|
||||
|
||||
struct DecVtxFormat {
|
||||
u8 w0fmt; u8 w0off; // first 4 weights
|
||||
u8 w1fmt; u8 w1off; // second 4 weights
|
||||
u8 uvfmt; u8 uvoff;
|
||||
u8 c0fmt; u8 c0off; // First color
|
||||
u8 c1fmt; u8 c1off;
|
||||
@@ -345,6 +347,61 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void ReadWeights(float weights[8]) const {
|
||||
const float *f = (const float *)(data_ + decFmt_.w0off);
|
||||
const u8 *b = (const u8 *)(data_ + decFmt_.w0off);
|
||||
const u16 *s = (const u16 *)(data_ + decFmt_.w0off);
|
||||
switch (decFmt_.w0fmt) {
|
||||
case DEC_FLOAT_1:
|
||||
case DEC_FLOAT_2:
|
||||
case DEC_FLOAT_3:
|
||||
case DEC_FLOAT_4:
|
||||
for (int i = 0; i <= decFmt_.w0fmt - DEC_FLOAT_1; i++)
|
||||
weights[i] = f[i];
|
||||
break;
|
||||
case DEC_U8_1: weights[0] = b[0] * (1.f / 128.f); break;
|
||||
case DEC_U8_2: for (int i = 0; i < 2; i++) weights[i] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U8_3: for (int i = 0; i < 3; i++) weights[i] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U8_4: for (int i = 0; i < 4; i++) weights[i] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U16_1: weights[0] = s[0] * (1.f / 32768.f); break;
|
||||
case DEC_U16_2: for (int i = 0; i < 2; i++) weights[i] = s[i] * (1.f / 32768.f); break;
|
||||
case DEC_U16_3: for (int i = 0; i < 3; i++) weights[i] = s[i] * (1.f / 32768.f); break;
|
||||
case DEC_U16_4: for (int i = 0; i < 4; i++) weights[i] = s[i] * (1.f / 32768.f); break;
|
||||
default:
|
||||
ERROR_LOG_REPORT_ONCE(fmtw0, G3D, "Reader: Unsupported W0 Format %d", decFmt_.w0fmt);
|
||||
memset(weights, 0, sizeof(float) * 8);
|
||||
break;
|
||||
}
|
||||
|
||||
f = (const float *)(data_ + decFmt_.w1off);
|
||||
b = (const u8 *)(data_ + decFmt_.w1off);
|
||||
s = (const u16 *)(data_ + decFmt_.w1off);
|
||||
switch (decFmt_.w1fmt) {
|
||||
case 0:
|
||||
// It's fine for there to be w0 weights but not w1.
|
||||
break;
|
||||
case DEC_FLOAT_1:
|
||||
case DEC_FLOAT_2:
|
||||
case DEC_FLOAT_3:
|
||||
case DEC_FLOAT_4:
|
||||
for (int i = 0; i <= decFmt_.w1fmt - DEC_FLOAT_1; i++)
|
||||
weights[i+4] = f[i];
|
||||
break;
|
||||
case DEC_U8_1: weights[4] = b[0] * (1.f / 128.f); break;
|
||||
case DEC_U8_2: for (int i = 0; i < 2; i++) weights[i+4] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U8_3: for (int i = 0; i < 3; i++) weights[i+4] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U8_4: for (int i = 0; i < 4; i++) weights[i+4] = b[i] * (1.f / 128.f); break;
|
||||
case DEC_U16_1: weights[4] = s[0] * (1.f / 32768.f); break;
|
||||
case DEC_U16_2: for (int i = 0; i < 2; i++) weights[i+4] = s[i] * (1.f / 32768.f); break;
|
||||
case DEC_U16_3: for (int i = 0; i < 3; i++) weights[i+4] = s[i] * (1.f / 32768.f); break;
|
||||
case DEC_U16_4: for (int i = 0; i < 4; i++) weights[i+4] = s[i] * (1.f / 32768.f); break;
|
||||
default:
|
||||
ERROR_LOG_REPORT_ONCE(fmtw1, G3D, "Reader: Unsupported W1 Format %d", decFmt_.w1fmt);
|
||||
memset(weights + 4, 0, sizeof(float) * 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasColor0() const { return decFmt_.c0fmt != 0; }
|
||||
bool hasColor1() const { return decFmt_.c1fmt != 0; }
|
||||
bool hasNormal() const { return decFmt_.nrmfmt != 0; }
|
||||
@@ -375,6 +432,9 @@ struct JitLookup {
|
||||
JitStepFunction jitFunc;
|
||||
};
|
||||
|
||||
// Collapse to less skinning shaders to reduce shader switching, which is expensive.
|
||||
int TranslateNumBones(int bones);
|
||||
|
||||
typedef void(*JittedVertexDecoder)(const u8 *src, u8 *dst, int count);
|
||||
|
||||
struct VertexDecoderOptions {
|
||||
@@ -401,6 +461,12 @@ public:
|
||||
|
||||
std::string GetString(DebugShaderStringType stringType);
|
||||
|
||||
void Step_WeightsU8() const;
|
||||
void Step_WeightsU16() const;
|
||||
void Step_WeightsU8ToFloat() const;
|
||||
void Step_WeightsU16ToFloat() const;
|
||||
void Step_WeightsFloat() const;
|
||||
|
||||
void ComputeSkinMatrix(const float weights[8]) const;
|
||||
|
||||
void Step_WeightsU8Skin() const;
|
||||
@@ -553,6 +619,12 @@ public:
|
||||
JittedVertexDecoder Compile(const VertexDecoder &dec, int32_t *jittedSize);
|
||||
void Clear();
|
||||
|
||||
void Jit_WeightsU8();
|
||||
void Jit_WeightsU16();
|
||||
void Jit_WeightsU8ToFloat();
|
||||
void Jit_WeightsU16ToFloat();
|
||||
void Jit_WeightsFloat();
|
||||
|
||||
void Jit_WeightsU8Skin();
|
||||
void Jit_WeightsU16Skin();
|
||||
void Jit_WeightsFloatSkin();
|
||||
|
||||
@@ -94,10 +94,16 @@ static const X64Reg fpScratchReg4 = XMM4;
|
||||
// on the interpreter if the compiler fails.
|
||||
|
||||
static const JitLookup jitLookup[] = {
|
||||
{&VertexDecoder::Step_WeightsU8, &VertexDecoderJitCache::Jit_WeightsU8},
|
||||
{&VertexDecoder::Step_WeightsU16, &VertexDecoderJitCache::Jit_WeightsU16},
|
||||
{&VertexDecoder::Step_WeightsFloat, &VertexDecoderJitCache::Jit_WeightsFloat},
|
||||
{&VertexDecoder::Step_WeightsU8Skin, &VertexDecoderJitCache::Jit_WeightsU8Skin},
|
||||
{&VertexDecoder::Step_WeightsU16Skin, &VertexDecoderJitCache::Jit_WeightsU16Skin},
|
||||
{&VertexDecoder::Step_WeightsFloatSkin, &VertexDecoderJitCache::Jit_WeightsFloatSkin},
|
||||
|
||||
{&VertexDecoder::Step_WeightsU8ToFloat, &VertexDecoderJitCache::Jit_WeightsU8ToFloat},
|
||||
{&VertexDecoder::Step_WeightsU16ToFloat, &VertexDecoderJitCache::Jit_WeightsU16ToFloat},
|
||||
|
||||
{&VertexDecoder::Step_TcFloat, &VertexDecoderJitCache::Jit_TcFloat},
|
||||
{&VertexDecoder::Step_TcU8ToFloat, &VertexDecoderJitCache::Jit_TcU8ToFloat},
|
||||
{&VertexDecoder::Step_TcU16ToFloat, &VertexDecoderJitCache::Jit_TcU16ToFloat},
|
||||
@@ -202,7 +208,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
|
||||
// Add code to convert matrices to 4x4.
|
||||
// Later we might want to do this when the matrices are loaded instead.
|
||||
int boneCount = 0;
|
||||
if (dec.weighttype) {
|
||||
if (dec.weighttype && g_Config.bSoftwareSkinning) {
|
||||
MOV(PTRBITS, R(tempReg1), ImmPtr(&threeMasks));
|
||||
MOVAPS(XMM4, MatR(tempReg1));
|
||||
MOV(PTRBITS, R(tempReg1), ImmPtr(&aOne));
|
||||
@@ -276,6 +282,175 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
|
||||
return (JittedVertexDecoder)start;
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8() {
|
||||
switch (dec_->nweights) {
|
||||
case 1:
|
||||
MOVZX(32, 8, tempReg1, MDisp(srcReg, dec_->weightoff));
|
||||
break;
|
||||
case 2:
|
||||
MOVZX(32, 16, tempReg1, MDisp(srcReg, dec_->weightoff));
|
||||
break;
|
||||
case 3:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
AND(32, R(tempReg1), Imm32(0x00FFFFFF));
|
||||
break;
|
||||
case 4:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
break;
|
||||
case 5:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOVZX(32, 8, tempReg2, MDisp(srcReg, dec_->weightoff + 4));
|
||||
break;
|
||||
case 6:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOVZX(32, 16, tempReg2, MDisp(srcReg, dec_->weightoff + 4));
|
||||
break;
|
||||
case 7:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOV(32, R(tempReg2), MDisp(srcReg, dec_->weightoff + 4));
|
||||
AND(32, R(tempReg2), Imm32(0x00FFFFFF));
|
||||
break;
|
||||
case 8:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOV(32, R(tempReg2), MDisp(srcReg, dec_->weightoff + 4));
|
||||
break;
|
||||
}
|
||||
|
||||
if (dec_->nweights <= 4) {
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
} else {
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w1off), R(tempReg2));
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU16() {
|
||||
switch (dec_->nweights) {
|
||||
case 1:
|
||||
MOVZX(32, 16, tempReg1, MDisp(srcReg, dec_->weightoff));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + 4), Imm32(0));
|
||||
return;
|
||||
|
||||
case 2:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + 4), Imm32(0));
|
||||
return;
|
||||
|
||||
case 3:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOVZX(32, 16, tempReg2, MDisp(srcReg, dec_->weightoff + 4));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + 4), R(tempReg2));
|
||||
return;
|
||||
|
||||
case 4:
|
||||
// Anything above 4 will do 4 here, and then the rest after.
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff));
|
||||
MOV(32, R(tempReg2), MDisp(srcReg, dec_->weightoff + 4));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off), R(tempReg1));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + 4), R(tempReg2));
|
||||
break;
|
||||
}
|
||||
|
||||
// Basic implementation - a short at a time. TODO: Optimize
|
||||
int j;
|
||||
for (j = 4; j < dec_->nweights; j++) {
|
||||
MOV(16, R(tempReg1), MDisp(srcReg, dec_->weightoff + j * 2));
|
||||
MOV(16, MDisp(dstReg, dec_->decFmt.w0off + j * 2), R(tempReg1));
|
||||
}
|
||||
while (j & 3) {
|
||||
MOV(16, MDisp(dstReg, dec_->decFmt.w0off + j * 2), Imm16(0));
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8ToFloat() {
|
||||
if (dec_->nweights >= 4) {
|
||||
Jit_AnyU8ToFloat(dec_->weightoff, 32);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
if (dec_->nweights > 4) {
|
||||
Jit_AnyU8ToFloat(dec_->weightoff + 4, (dec_->nweights - 4) * 8);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w1off), XMM3);
|
||||
}
|
||||
} else {
|
||||
Jit_AnyU8ToFloat(dec_->weightoff, dec_->nweights * 8);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU16ToFloat() {
|
||||
if (dec_->nweights >= 4) {
|
||||
Jit_AnyU16ToFloat(dec_->weightoff, 64);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
if (dec_->nweights > 4) {
|
||||
Jit_AnyU16ToFloat(dec_->weightoff + 4 * 2, (dec_->nweights - 4) * 16);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w1off), XMM3);
|
||||
}
|
||||
} else {
|
||||
Jit_AnyU16ToFloat(dec_->weightoff, dec_->nweights * 16);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsFloat() {
|
||||
int j;
|
||||
switch (dec_->nweights) {
|
||||
case 1:
|
||||
// MOVSS: When the source operand is a memory location and destination operand is an XMM register, the three high-order doublewords of the destination operand are cleared to all 0s.
|
||||
MOVSS(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
MOVQ_xmm(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
MOVUPS(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
MOVUPS(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVSS(XMM4, MDisp(srcReg, dec_->weightoff + 16));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off + 16), XMM4);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
MOVUPS(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVQ_xmm(XMM4, MDisp(srcReg, dec_->weightoff + 16));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off + 16), XMM4);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
MOVUPS(XMM3, MDisp(srcReg, dec_->weightoff));
|
||||
MOVUPS(XMM4, MDisp(srcReg, dec_->weightoff + 16));
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off), XMM3);
|
||||
MOVUPS(MDisp(dstReg, dec_->decFmt.w0off + 16), XMM4);
|
||||
break;
|
||||
|
||||
default:
|
||||
for (j = 0; j < dec_->nweights; j++) {
|
||||
MOV(32, R(tempReg1), MDisp(srcReg, dec_->weightoff + j * 4));
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + j * 4), R(tempReg1));
|
||||
}
|
||||
while (j & 3) { // Zero additional weights rounding up to 4.
|
||||
MOV(32, MDisp(dstReg, dec_->decFmt.w0off + j * 4), Imm32(0));
|
||||
j++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VertexDecoderJitCache::Jit_WeightsU8Skin() {
|
||||
MOV(PTRBITS, R(tempReg2), ImmPtr(&bones));
|
||||
|
||||
|
||||
@@ -204,6 +204,18 @@ ID3D11InputLayout *DrawEngineD3D11::SetupDecFmtForDraw(D3D11VertexShader *vshade
|
||||
D3D11_INPUT_ELEMENT_DESC VertexElements[8];
|
||||
D3D11_INPUT_ELEMENT_DESC *VertexElement = &VertexElements[0];
|
||||
|
||||
// Vertices Elements orders
|
||||
// WEIGHT
|
||||
if (decFmt.w0fmt != 0) {
|
||||
VertexAttribSetup(VertexElement, decFmt.w0fmt, decFmt.w0off, "TEXCOORD", 1);
|
||||
VertexElement++;
|
||||
}
|
||||
|
||||
if (decFmt.w1fmt != 0) {
|
||||
VertexAttribSetup(VertexElement, decFmt.w1fmt, decFmt.w1off, "TEXCOORD", 2);
|
||||
VertexElement++;
|
||||
}
|
||||
|
||||
// TC
|
||||
if (decFmt.uvfmt != 0) {
|
||||
VertexAttribSetup(VertexElement, decFmt.uvfmt, decFmt.uvoff, "TEXCOORD", 0);
|
||||
@@ -331,8 +343,8 @@ void DrawEngineD3D11::DoFlush() {
|
||||
|
||||
// Cannot cache vertex data with morph enabled.
|
||||
bool useCache = g_Config.bVertexCache && !(lastVType_ & GE_VTYPE_MORPHCOUNT_MASK);
|
||||
// Also avoid caching when skinning.
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK)
|
||||
// Also avoid caching when software skinning.
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK))
|
||||
useCache = false;
|
||||
|
||||
if (useCache) {
|
||||
|
||||
@@ -103,6 +103,7 @@ GPU_D3D11::GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
|
||||
// No need to flush before the tex scale/offset commands if we are baking
|
||||
// the tex scale/offset into the vertices anyway.
|
||||
UpdateCmdInfo();
|
||||
CheckGPUFeatures();
|
||||
|
||||
BuildReportingInfo();
|
||||
@@ -214,6 +215,7 @@ void GPU_D3D11::InitClear() {
|
||||
|
||||
void GPU_D3D11::BeginHostFrame() {
|
||||
GPUCommon::BeginHostFrame();
|
||||
UpdateCmdInfo();
|
||||
if (resized_) {
|
||||
CheckGPUFeatures();
|
||||
framebufferManager_->Resized();
|
||||
|
||||
@@ -93,19 +93,24 @@ ShaderManagerD3D11::ShaderManagerD3D11(ID3D11Device *device, ID3D11DeviceContext
|
||||
codeBuffer_ = new char[16384];
|
||||
memset(&ub_base, 0, sizeof(ub_base));
|
||||
memset(&ub_lights, 0, sizeof(ub_lights));
|
||||
memset(&ub_bones, 0, sizeof(ub_bones));
|
||||
|
||||
INFO_LOG(G3D, "sizeof(ub_base): %d", (int)sizeof(ub_base));
|
||||
INFO_LOG(G3D, "sizeof(ub_lights): %d", (int)sizeof(ub_lights));
|
||||
INFO_LOG(G3D, "sizeof(ub_bones): %d", (int)sizeof(ub_bones));
|
||||
|
||||
D3D11_BUFFER_DESC desc{sizeof(ub_base), D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE };
|
||||
ASSERT_SUCCESS(device_->CreateBuffer(&desc, nullptr, &push_base));
|
||||
desc.ByteWidth = sizeof(ub_lights);
|
||||
ASSERT_SUCCESS(device_->CreateBuffer(&desc, nullptr, &push_lights));
|
||||
desc.ByteWidth = sizeof(ub_bones);
|
||||
ASSERT_SUCCESS(device_->CreateBuffer(&desc, nullptr, &push_bones));
|
||||
}
|
||||
|
||||
ShaderManagerD3D11::~ShaderManagerD3D11() {
|
||||
push_base->Release();
|
||||
push_lights->Release();
|
||||
push_bones->Release();
|
||||
ClearShaders();
|
||||
delete[] codeBuffer_;
|
||||
}
|
||||
@@ -154,15 +159,21 @@ uint64_t ShaderManagerD3D11::UpdateUniforms() {
|
||||
memcpy(map.pData, &ub_lights, sizeof(ub_lights));
|
||||
context_->Unmap(push_lights, 0);
|
||||
}
|
||||
if (dirty & DIRTY_BONE_UNIFORMS) {
|
||||
BoneUpdateUniforms(&ub_bones, dirty);
|
||||
context_->Map(push_bones, 0, D3D11_MAP_WRITE_DISCARD, 0, &map);
|
||||
memcpy(map.pData, &ub_bones, sizeof(ub_bones));
|
||||
context_->Unmap(push_bones, 0);
|
||||
}
|
||||
}
|
||||
gstate_c.CleanUniforms();
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void ShaderManagerD3D11::BindUniforms() {
|
||||
ID3D11Buffer *vs_cbs[2] = { push_base, push_lights };
|
||||
ID3D11Buffer *vs_cbs[3] = { push_base, push_lights, push_bones };
|
||||
ID3D11Buffer *ps_cbs[1] = { push_base };
|
||||
context_->VSSetConstantBuffers(0, 2, vs_cbs);
|
||||
context_->VSSetConstantBuffers(0, 3, vs_cbs);
|
||||
context_->PSSetConstantBuffers(0, 1, ps_cbs);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
// Applies dirty changes and copies the buffer.
|
||||
bool IsBaseDirty() { return true; }
|
||||
bool IsLightDirty() { return true; }
|
||||
bool IsBoneDirty() { return true; }
|
||||
|
||||
private:
|
||||
void Clear();
|
||||
@@ -120,10 +121,12 @@ private:
|
||||
// Uniform block scratchpad. These (the relevant ones) are copied to the current pushbuffer at draw time.
|
||||
UB_VS_FS_Base ub_base;
|
||||
UB_VS_Lights ub_lights;
|
||||
UB_VS_Bones ub_bones;
|
||||
|
||||
// Not actual pushbuffers, requires D3D11.1, let's try to live without that first.
|
||||
ID3D11Buffer *push_base;
|
||||
ID3D11Buffer *push_lights;
|
||||
ID3D11Buffer *push_bones;
|
||||
|
||||
D3D11FragmentShader *lastFShader_;
|
||||
D3D11VertexShader *lastVShader_;
|
||||
|
||||
@@ -175,6 +175,16 @@ IDirect3DVertexDeclaration9 *DrawEngineDX9::SetupDecFmtForDraw(VSShader *vshader
|
||||
D3DVERTEXELEMENT9 *VertexElement = &VertexElements[0];
|
||||
|
||||
// Vertices Elements orders
|
||||
// WEIGHT
|
||||
if (decFmt.w0fmt != 0) {
|
||||
VertexAttribSetup(VertexElement, decFmt.w0fmt, decFmt.w0off, D3DDECLUSAGE_TEXCOORD, 1);
|
||||
VertexElement++;
|
||||
}
|
||||
|
||||
if (decFmt.w1fmt != 0) {
|
||||
VertexAttribSetup(VertexElement, decFmt.w1fmt, decFmt.w1off, D3DDECLUSAGE_TEXCOORD, 2);
|
||||
VertexElement++;
|
||||
}
|
||||
|
||||
// TC
|
||||
if (decFmt.uvfmt != 0) {
|
||||
@@ -314,8 +324,8 @@ void DrawEngineDX9::DoFlush() {
|
||||
|
||||
// Cannot cache vertex data with morph enabled.
|
||||
bool useCache = g_Config.bVertexCache && !(lastVType_ & GE_VTYPE_MORPHCOUNT_MASK);
|
||||
// Also avoid caching when skinning.
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK)
|
||||
// Also avoid caching when software skinning.
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK))
|
||||
useCache = false;
|
||||
|
||||
if (useCache) {
|
||||
|
||||
@@ -83,6 +83,9 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
ERROR_LOG(G3D, "gstate has drifted out of sync!");
|
||||
}
|
||||
|
||||
// No need to flush before the tex scale/offset commands if we are baking
|
||||
// the tex scale/offset into the vertices anyway.
|
||||
UpdateCmdInfo();
|
||||
CheckGPUFeatures();
|
||||
|
||||
BuildReportingInfo();
|
||||
@@ -188,6 +191,7 @@ void GPU_DX9::InitClear() {
|
||||
|
||||
void GPU_DX9::BeginHostFrame() {
|
||||
GPUCommon::BeginHostFrame();
|
||||
UpdateCmdInfo();
|
||||
if (resized_) {
|
||||
CheckGPUFeatures();
|
||||
framebufferManager_->Resized();
|
||||
|
||||
@@ -324,7 +324,7 @@ void ShaderManagerDX9::PSUpdateUniforms(u64 dirtyUniforms) {
|
||||
}
|
||||
|
||||
const uint64_t vsUniforms = DIRTY_PROJMATRIX | DIRTY_PROJTHROUGHMATRIX | DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX | DIRTY_TEXMATRIX |
|
||||
DIRTY_FOGCOEF | DIRTY_UVSCALEOFFSET | DIRTY_DEPTHRANGE |
|
||||
DIRTY_FOGCOEF | DIRTY_BONE_UNIFORMS | DIRTY_UVSCALEOFFSET | DIRTY_DEPTHRANGE |
|
||||
DIRTY_AMBIENT | DIRTY_MATAMBIENTALPHA | DIRTY_MATSPECULAR | DIRTY_MATDIFFUSE | DIRTY_MATEMISSIVE | DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3;
|
||||
|
||||
void ShaderManagerDX9::VSUpdateUniforms(u64 dirtyUniforms) {
|
||||
@@ -399,6 +399,39 @@ void ShaderManagerDX9::VSUpdateUniforms(u64 dirtyUniforms) {
|
||||
VSSetFloatUniform4(CONST_VS_GUARDBAND, gb);
|
||||
}
|
||||
|
||||
// TODO: Could even set all bones in one go if they're all dirty.
|
||||
#ifdef USE_BONE_ARRAY
|
||||
if (u_bone != 0) {
|
||||
float allBones[8 * 16];
|
||||
|
||||
bool allDirty = true;
|
||||
for (int i = 0; i < numBones; i++) {
|
||||
if (dirtyUniforms & (DIRTY_BONEMATRIX0 << i)) {
|
||||
ConvertMatrix4x3To4x4(allBones + 16 * i, gstate.boneMatrix + 12 * i);
|
||||
} else {
|
||||
allDirty = false;
|
||||
}
|
||||
}
|
||||
if (allDirty) {
|
||||
// Set them all with one call
|
||||
//glUniformMatrix4fv(u_bone, numBones, GL_FALSE, allBones);
|
||||
} else {
|
||||
// Set them one by one. Could try to coalesce two in a row etc but too lazy.
|
||||
for (int i = 0; i < numBones; i++) {
|
||||
if (dirtyUniforms & (DIRTY_BONEMATRIX0 << i)) {
|
||||
//glUniformMatrix4fv(u_bone + i, 1, GL_FALSE, allBones + 16 * i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (dirtyUniforms & (DIRTY_BONEMATRIX0 << i)) {
|
||||
VSSetMatrix4x3_3(CONST_VS_BONE0 + 3 * i, gstate.boneMatrix + 12 * i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Texturing
|
||||
if (dirtyUniforms & DIRTY_UVSCALEOFFSET) {
|
||||
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
|
||||
|
||||
@@ -38,6 +38,18 @@
|
||||
|
||||
namespace DX9 {
|
||||
|
||||
static const char * const boneWeightAttrDecl[9] = {
|
||||
"#ERROR#",
|
||||
"float a_w1:TEXCOORD1;\n",
|
||||
"float2 a_w1:TEXCOORD1;\n",
|
||||
"float3 a_w1:TEXCOORD1;\n",
|
||||
"float4 a_w1:TEXCOORD1;\n",
|
||||
"float4 a_w1:TEXCOORD1;\n float a_w2:TEXCOORD2;\n",
|
||||
"float4 a_w1:TEXCOORD1;\n float2 a_w2:TEXCOORD2;\n",
|
||||
"float4 a_w1:TEXCOORD1;\n float3 a_w2:TEXCOORD2;\n",
|
||||
"float4 a_w1:TEXCOORD1;\n float4 a_w2:TEXCOORD2;\n",
|
||||
};
|
||||
|
||||
enum DoLightComputation {
|
||||
LIGHT_OFF,
|
||||
LIGHT_SHADE,
|
||||
@@ -89,6 +101,7 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
bool flipNormal = id.Bit(VS_BIT_NORM_REVERSE);
|
||||
int ls0 = id.Bits(VS_BIT_LS0, 2);
|
||||
int ls1 = id.Bits(VS_BIT_LS1, 2);
|
||||
bool enableBones = id.Bit(VS_BIT_ENABLE_BONES);
|
||||
bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE);
|
||||
int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3);
|
||||
|
||||
@@ -110,6 +123,12 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
}
|
||||
}
|
||||
|
||||
int numBoneWeights = 0;
|
||||
int boneWeightScale = id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2);
|
||||
if (enableBones) {
|
||||
numBoneWeights = 1 + id.Bits(VS_BIT_BONES, 3);
|
||||
}
|
||||
|
||||
if (lang == HLSL_DX9) {
|
||||
WRITE(p, "#pragma warning( disable : 3571 )\n");
|
||||
if (isModeThrough) {
|
||||
@@ -132,6 +151,15 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
WRITE(p, "float4x3 u_view : register(c%i);\n", CONST_VS_VIEW);
|
||||
if (doTextureTransform)
|
||||
WRITE(p, "float4x3 u_tex : register(c%i);\n", CONST_VS_TEXMTX);
|
||||
if (enableBones) {
|
||||
#ifdef USE_BONE_ARRAY
|
||||
WRITE(p, "float4x3 u_bone[%i] : register(c%i);\n", numBones, CONST_VS_BONE0);
|
||||
#else
|
||||
for (int i = 0; i < numBoneWeights; i++) {
|
||||
WRITE(p, "float4x3 u_bone%i : register(c%i);\n", i, CONST_VS_BONE0 + i * 3);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (doTexture) {
|
||||
WRITE(p, "float4 u_uvscaleoffset : register(c%i);\n", CONST_VS_UVSCALEOFFSET);
|
||||
}
|
||||
@@ -175,6 +203,7 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
} else {
|
||||
WRITE(p, "cbuffer base : register(b0) {\n%s};\n", cb_baseStr);
|
||||
WRITE(p, "cbuffer lights: register(b1) {\n%s};\n", cb_vs_lightsStr);
|
||||
WRITE(p, "cbuffer bones : register(b2) {\n%s};\n", cb_vs_bonesStr);
|
||||
}
|
||||
|
||||
// And the "varyings".
|
||||
@@ -184,6 +213,9 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
if ((doSpline || doBezier) && lang == HLSL_D3D11) {
|
||||
WRITE(p, " uint instanceId : SV_InstanceID;\n");
|
||||
}
|
||||
if (enableBones) {
|
||||
WRITE(p, " %s", boneWeightAttrDecl[numBoneWeights]);
|
||||
}
|
||||
if (doTexture && hasTexcoord) {
|
||||
WRITE(p, " float2 texcoord : TEXCOORD0;\n");
|
||||
}
|
||||
@@ -387,113 +419,188 @@ void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage
|
||||
}
|
||||
WRITE(p, " Out.gl_Position = outPos;\n");
|
||||
} else {
|
||||
// Step 1: World Transform
|
||||
// Hardware tessellation
|
||||
if (doSpline || doBezier) {
|
||||
WRITE(p, " uint num_patches_u = %s;\n", doBezier ? "(u_spline_count_u - 1) / 3u" : "u_spline_count_u - 3");
|
||||
WRITE(p, " float2 tess_pos = In.position.xy;\n");
|
||||
WRITE(p, " int u = In.instanceId %% num_patches_u;\n");
|
||||
WRITE(p, " int v = In.instanceId / num_patches_u;\n");
|
||||
WRITE(p, " int2 patch_pos = int2(u, v);\n");
|
||||
WRITE(p, " float3 _pos[16];\n");
|
||||
WRITE(p, " float2 _tex[16];\n");
|
||||
WRITE(p, " float4 _col[16];\n");
|
||||
WRITE(p, " int idx;\n");
|
||||
WRITE(p, " int2 index;\n");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
WRITE(p, " idx = (%i + v%s) * u_spline_count_u + (%i + u%s);\n", i, doBezier ? " * 3" : "", j, doBezier ? " * 3" : "");
|
||||
WRITE(p, " index = int2(idx, 0);\n");
|
||||
WRITE(p, " _pos[%i] = u_tess_pos_tex.Load(index).xyz;\n", i * 4 + j);
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[%i] = u_tess_tex_tex.Load(index).xy;\n", i * 4 + j);
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[%i] = u_tess_col_tex.Load(index).rgba;\n", i * 4 + j);
|
||||
// Step 1: World Transform / Skinning
|
||||
if (!enableBones) {
|
||||
// Hardware tessellation
|
||||
if (doSpline || doBezier) {
|
||||
WRITE(p, " uint u_spline_count_u = u_spline_counts & 0xFF;\n");
|
||||
WRITE(p, " uint u_spline_count_v = (u_spline_counts >> 8) & 0xFF;\n");
|
||||
WRITE(p, " uint num_patches_u = %s;\n", doBezier ? "(u_spline_count_u - 1) / 3u" : "u_spline_count_u - 3");
|
||||
WRITE(p, " float2 tess_pos = In.position.xy;\n");
|
||||
WRITE(p, " int u = In.instanceId %% num_patches_u;\n");
|
||||
WRITE(p, " int v = In.instanceId / num_patches_u;\n");
|
||||
WRITE(p, " int2 patch_pos = int2(u, v);\n");
|
||||
WRITE(p, " float3 _pos[16];\n");
|
||||
WRITE(p, " float2 _tex[16];\n");
|
||||
WRITE(p, " float4 _col[16];\n");
|
||||
WRITE(p, " int idx;\n");
|
||||
WRITE(p, " int2 index;\n");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
WRITE(p, " idx = (%i + v%s) * u_spline_count_u + (%i + u%s);\n", i, doBezier ? " * 3" : "", j, doBezier ? " * 3" : "");
|
||||
WRITE(p, " index = int2(idx, 0);\n");
|
||||
WRITE(p, " _pos[%i] = u_tess_pos_tex.Load(index).xyz;\n", i * 4 + j);
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[%i] = u_tess_tex_tex.Load(index).xy;\n", i * 4 + j);
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[%i] = u_tess_col_tex.Load(index).rgba;\n", i * 4 + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
WRITE(p, " float2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1.0 - tess_pos) * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3.0 * tess_pos * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3.0 * tess_pos * tess_pos * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else if (doSpline) {
|
||||
WRITE(p, " int2 spline_num_patches = int2(u_spline_count_u - 3, u_spline_count_v - 3);\n");
|
||||
WRITE(p, " int2 spline_type = int2(u_spline_type_u, u_spline_type_v);\n");
|
||||
WRITE(p, " float2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + patch_pos, knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " float3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " float2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " float2 tex = tess_pos + patch_pos;\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " float4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " float4 col = u_tess_col_tex.Load(int2(0, 0)).rgba;\n");
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
WRITE(p, " float2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " float2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3.0 * (tess_pos - 1.0) * (tess_pos - 1.0); \n");
|
||||
WRITE(p, " bernderiv[1] = 9.0 * tess_pos * tess_pos - 12.0 * tess_pos + 3.0; \n");
|
||||
WRITE(p, " bernderiv[2] = 3.0 * (2.0 - 3.0 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3.0 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " float2 bernderiv_u[4];\n");
|
||||
WRITE(p, " float2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = float2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = float2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " float3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " float3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1.0 - tess_pos) * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3.0 * tess_pos * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3.0 * tess_pos * tess_pos * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else if (doSpline) {
|
||||
WRITE(p, " float2 tess_next_u = float2(In.normal.x, 0.0);\n");
|
||||
WRITE(p, " float2 tess_next_v = float2(0.0, In.normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " float2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " float2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " float2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " float2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_u = tess_sample(_pos, weights);\n");
|
||||
|
||||
WRITE(p, " float3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " float3 dv = pos_d - pos_u;\n");
|
||||
WRITE(p, " int2 spline_num_patches = int2(u_spline_count_u - 3, u_spline_count_v - 3);\n");
|
||||
WRITE(p, " int u_spline_type_u = (u_spline_counts >> 16) & 0xFF;\n");
|
||||
WRITE(p, " int u_spline_type_v = (u_spline_counts >> 24) & 0xFF;\n");
|
||||
WRITE(p, " int2 spline_type = int2(u_spline_type_u, u_spline_type_v);\n");
|
||||
WRITE(p, " float2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + patch_pos, knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " float3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
WRITE(p, " float3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " float2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " float2 tex = tess_pos + patch_pos;\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " float4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " float4 col = u_tess_col_tex.Load(int2(0, 0)).rgba;\n");
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " float2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3.0 * (tess_pos - 1.0) * (tess_pos - 1.0); \n");
|
||||
WRITE(p, " bernderiv[1] = 9.0 * tess_pos * tess_pos - 12.0 * tess_pos + 3.0; \n");
|
||||
WRITE(p, " bernderiv[2] = 3.0 * (2.0 - 3.0 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3.0 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " float2 bernderiv_u[4];\n");
|
||||
WRITE(p, " float2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = float2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = float2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " float3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " float3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
} else if (doSpline) {
|
||||
WRITE(p, " float2 tess_next_u = float2(In.normal.x, 0.0);\n");
|
||||
WRITE(p, " float2 tess_next_v = float2(0.0, In.normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " float2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " float2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " float2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " float2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " float3 pos_u = tess_sample(_pos, weights);\n");
|
||||
|
||||
WRITE(p, " float3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " float3 dv = pos_d - pos_u;\n");
|
||||
}
|
||||
WRITE(p, " float3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
}
|
||||
WRITE(p, " float3 worldpos = mul(float4(pos.xyz, 1.0), u_world);\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " float3 worldnormal = normalize(mul(float4(%snrm, 0.0), u_world));\n", flipNormalTess ? "-" : "");
|
||||
else
|
||||
WRITE(p, " float3 worldnormal = float3(0.0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
// No skinning, just standard T&L.
|
||||
WRITE(p, " float3 worldpos = mul(float4(In.position.xyz, 1.0), u_world);\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " float3 worldnormal = normalize(mul(float4(%sIn.normal, 0.0), u_world));\n", flipNormal ? "-" : "");
|
||||
else
|
||||
WRITE(p, " float3 worldnormal = float3(0.0, 0.0, 1.0);\n");
|
||||
}
|
||||
WRITE(p, " float3 worldpos = mul(float4(pos.xyz, 1.0), u_world);\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " float3 worldnormal = normalize(mul(float4(%snrm, 0.0), u_world));\n", flipNormalTess ? "-" : "");
|
||||
else
|
||||
WRITE(p, " float3 worldnormal = float3(0.0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
WRITE(p, " float3 worldpos = mul(float4(In.position.xyz, 1.0), u_world);\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " float3 worldnormal = normalize(mul(float4(%sIn.normal, 0.0), u_world));\n", flipNormal ? "-" : "");
|
||||
else
|
||||
WRITE(p, " float3 worldnormal = float3(0.0, 0.0, 1.0);\n");
|
||||
static const char * const boneWeightAttr[8] = {
|
||||
"a_w1.x", "a_w1.y", "a_w1.z", "a_w1.w",
|
||||
"a_w2.x", "a_w2.y", "a_w2.z", "a_w2.w",
|
||||
};
|
||||
|
||||
#if defined(USE_FOR_LOOP) && defined(USE_BONE_ARRAY)
|
||||
|
||||
// To loop through the weights, we unfortunately need to put them in a float array.
|
||||
// GLSL ES sucks - no way to directly initialize an array!
|
||||
switch (numBoneWeights) {
|
||||
case 1: WRITE(p, " float w[1]; w[0] = a_w1;\n"); break;
|
||||
case 2: WRITE(p, " float w[2]; w[0] = a_w1.x; w[1] = a_w1.y;\n"); break;
|
||||
case 3: WRITE(p, " float w[3]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z;\n"); break;
|
||||
case 4: WRITE(p, " float w[4]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z; w[3] = a_w1.w;\n"); break;
|
||||
case 5: WRITE(p, " float w[5]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z; w[3] = a_w1.w; w[4] = a_w2;\n"); break;
|
||||
case 6: WRITE(p, " float w[6]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z; w[3] = a_w1.w; w[4] = a_w2.x; w[5] = a_w2.y;\n"); break;
|
||||
case 7: WRITE(p, " float w[7]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z; w[3] = a_w1.w; w[4] = a_w2.x; w[5] = a_w2.y; w[6] = a_w2.z;\n"); break;
|
||||
case 8: WRITE(p, " float w[8]; w[0] = a_w1.x; w[1] = a_w1.y; w[2] = a_w1.z; w[3] = a_w1.w; w[4] = a_w2.x; w[5] = a_w2.y; w[6] = a_w2.z; w[7] = a_w2.w;\n"); break;
|
||||
}
|
||||
|
||||
WRITE(p, " mat4 skinMatrix = w[0] * u_bone[0];\n");
|
||||
if (numBoneWeights > 1) {
|
||||
WRITE(p, " for (int i = 1; i < %i; i++) {\n", numBoneWeights);
|
||||
WRITE(p, " skinMatrix += w[i] * u_bone[i];\n");
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
|
||||
#else
|
||||
if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) {
|
||||
if (numBoneWeights == 1)
|
||||
WRITE(p, " float4x3 skinMatrix = mul(In.a_w1, u_bone[0])");
|
||||
else
|
||||
WRITE(p, " float4x3 skinMatrix = mul(In.a_w1.x, u_bone[0])");
|
||||
for (int i = 1; i < numBoneWeights; i++) {
|
||||
const char *weightAttr = boneWeightAttr[i];
|
||||
// workaround for "cant do .x of scalar" issue
|
||||
if (numBoneWeights == 1 && i == 0) weightAttr = "a_w1";
|
||||
if (numBoneWeights == 5 && i == 4) weightAttr = "a_w2";
|
||||
WRITE(p, " + mul(In.%s, u_bone[%i])", weightAttr, i);
|
||||
}
|
||||
} else {
|
||||
if (numBoneWeights == 1)
|
||||
WRITE(p, " float4x3 skinMatrix = mul(In.a_w1, u_bone0)");
|
||||
else
|
||||
WRITE(p, " float4x3 skinMatrix = mul(In.a_w1.x, u_bone0)");
|
||||
for (int i = 1; i < numBoneWeights; i++) {
|
||||
const char *weightAttr = boneWeightAttr[i];
|
||||
// workaround for "cant do .x of scalar" issue
|
||||
if (numBoneWeights == 1 && i == 0) weightAttr = "a_w1";
|
||||
if (numBoneWeights == 5 && i == 4) weightAttr = "a_w2";
|
||||
WRITE(p, " + mul(In.%s, u_bone%i)", weightAttr, i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WRITE(p, ";\n");
|
||||
|
||||
// Trying to simplify this results in bugs in LBP...
|
||||
WRITE(p, " float3 skinnedpos = mul(float4(In.position.xyz, 1.0), skinMatrix);\n");
|
||||
WRITE(p, " float3 worldpos = mul(float4(skinnedpos, 1.0), u_world);\n");
|
||||
|
||||
if (hasNormal) {
|
||||
WRITE(p, " float3 skinnednormal = mul(float4(%sIn.normal, 0.0), skinMatrix);\n", flipNormal ? "-" : "");
|
||||
} else {
|
||||
WRITE(p, " float3 skinnednormal = mul(float4(0.0, 0.0, %s1.0, 0.0), skinMatrix);\n", flipNormal ? "-" : "");
|
||||
}
|
||||
WRITE(p, " float3 worldnormal = normalize(mul(float4(skinnednormal, 0.0), u_world));\n");
|
||||
}
|
||||
|
||||
WRITE(p, " float4 viewPos = float4(mul(float4(worldpos, 1.0), u_view), 1.0);\n");
|
||||
|
||||
@@ -44,6 +44,15 @@ namespace DX9 {
|
||||
CONST_VS_LIGHTSPECULAR = 44,
|
||||
CONST_VS_LIGHTAMBIENT = 48,
|
||||
CONST_VS_DEPTHRANGE = 52,
|
||||
CONST_VS_BONE0 = 53,
|
||||
CONST_VS_BONE1 = 56,
|
||||
CONST_VS_BONE2 = 59,
|
||||
CONST_VS_BONE3 = 62,
|
||||
CONST_VS_BONE4 = 65,
|
||||
CONST_VS_BONE5 = 68,
|
||||
CONST_VS_BONE6 = 71,
|
||||
CONST_VS_BONE7 = 74,
|
||||
CONST_VS_BONE8 = 77,
|
||||
};
|
||||
#define CONST_VS_GUARDBAND 84
|
||||
|
||||
|
||||
@@ -211,6 +211,8 @@ GLRInputLayout *DrawEngineGLES::SetupDecFmtForDraw(LinkedShader *program, const
|
||||
}
|
||||
|
||||
std::vector<GLRInputLayout::Entry> entries;
|
||||
VertexAttribSetup(ATTR_W1, decFmt.w0fmt, decFmt.stride, decFmt.w0off, entries);
|
||||
VertexAttribSetup(ATTR_W2, decFmt.w1fmt, decFmt.stride, decFmt.w1off, entries);
|
||||
VertexAttribSetup(ATTR_TEXCOORD, decFmt.uvfmt, decFmt.stride, decFmt.uvoff, entries);
|
||||
VertexAttribSetup(ATTR_COLOR0, decFmt.c0fmt, decFmt.stride, decFmt.c0off, entries);
|
||||
VertexAttribSetup(ATTR_COLOR1, decFmt.c1fmt, decFmt.stride, decFmt.c1off, entries);
|
||||
@@ -322,8 +324,8 @@ void DrawEngineGLES::DoFlush() {
|
||||
|
||||
// Cannot cache vertex data with morph enabled.
|
||||
bool useCache = g_Config.bVertexCache && !(lastVType_ & GE_VTYPE_MORPHCOUNT_MASK);
|
||||
// Also avoid caching when skinning.
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK)
|
||||
// Also avoid caching when software skinning.
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK))
|
||||
useCache = false;
|
||||
|
||||
// TEMPORARY
|
||||
@@ -467,8 +469,8 @@ void DrawEngineGLES::DoFlush() {
|
||||
|
||||
vai->lastFrame = gpuStats.numFlips;
|
||||
} else {
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
// If skinning, we've already predecoded into "decoded". So push that content.
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
// If software skinning, we've already predecoded into "decoded". So push that content.
|
||||
size_t size = decodedVerts_ * dec_->GetDecVtxFmt().stride;
|
||||
u8 *dest = (u8 *)frameData.pushVertex->Push(size, &vertexBufferOffset, &vertexBuffer);
|
||||
memcpy(dest, decoded, size);
|
||||
@@ -655,7 +657,7 @@ void DrawEngineGLES::TessellationDataTransferGLES::SendDataToShader(const float
|
||||
data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D);
|
||||
renderManager_->TextureImage(data_tex[0], 0, size, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, pos_data, GLRAllocType::NEW, false);
|
||||
renderManager_->FinalizeTexture(data_tex[0], 0, false);
|
||||
renderManager_->BindTexture(4, data_tex[0]);
|
||||
renderManager_->BindTexture(TEX_SLOT_SPLINE_POS, data_tex[0]);
|
||||
|
||||
// Texcoords
|
||||
if (hasTexCoords) {
|
||||
@@ -666,7 +668,7 @@ void DrawEngineGLES::TessellationDataTransferGLES::SendDataToShader(const float
|
||||
data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D);
|
||||
renderManager_->TextureImage(data_tex[1], 0, size, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, tex_data, GLRAllocType::NEW, false);
|
||||
renderManager_->FinalizeTexture(data_tex[1], 0, false);
|
||||
renderManager_->BindTexture(5, data_tex[1]);
|
||||
renderManager_->BindTexture(TEX_SLOT_SPLINE_NRM, data_tex[1]);
|
||||
}
|
||||
|
||||
if (data_tex[2])
|
||||
@@ -678,7 +680,7 @@ void DrawEngineGLES::TessellationDataTransferGLES::SendDataToShader(const float
|
||||
|
||||
renderManager_->TextureImage(data_tex[2], 0, sizeColor, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, col_data, GLRAllocType::NEW, false);
|
||||
renderManager_->FinalizeTexture(data_tex[2], 0, false);
|
||||
renderManager_->BindTexture(6, data_tex[2]);
|
||||
renderManager_->BindTexture(TEX_SLOT_SPLINE_COL, data_tex[2]);
|
||||
}
|
||||
|
||||
void DrawEngineGLES::TessellationDataTransferGLES::EndFrame() {
|
||||
|
||||
@@ -41,6 +41,17 @@ struct TransformedVertex;
|
||||
|
||||
struct DecVtxFormat;
|
||||
|
||||
enum {
|
||||
TEX_SLOT_PSP_TEXTURE = 0,
|
||||
TEX_SLOT_SHADERBLEND_SRC = 1,
|
||||
TEX_SLOT_ALPHATEST = 2,
|
||||
TEX_SLOT_CLUT = 3,
|
||||
TEX_SLOT_SPLINE_POS = 4,
|
||||
TEX_SLOT_SPLINE_NRM = 5,
|
||||
TEX_SLOT_SPLINE_COL = 6,
|
||||
};
|
||||
|
||||
|
||||
// States transitions:
|
||||
// On creation: DRAWN_NEW
|
||||
// DRAWN_NEW -> DRAWN_HASHING
|
||||
|
||||
@@ -158,6 +158,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniform
|
||||
bool doTextureProjection = id.Bit(FS_BIT_DO_TEXTURE_PROJ);
|
||||
bool doTextureAlpha = id.Bit(FS_BIT_TEXALPHA);
|
||||
bool doFlatShading = id.Bit(FS_BIT_FLATSHADE);
|
||||
bool shaderDepal = id.Bit(FS_BIT_SHADER_DEPAL);
|
||||
|
||||
GEComparison alphaTestFunc = (GEComparison)id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3);
|
||||
GEComparison colorTestFunc = (GEComparison)id.Bits(FS_BIT_COLOR_TEST_FUNC, 2);
|
||||
@@ -218,6 +219,12 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniform
|
||||
}
|
||||
}
|
||||
|
||||
if (shaderDepal) {
|
||||
WRITE(p, "uniform sampler2D pal;\n");
|
||||
WRITE(p, "uniform int u_depal;\n");
|
||||
*uniformMask |= DIRTY_DEPAL;
|
||||
}
|
||||
|
||||
StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4);
|
||||
if (stencilToAlpha && replaceAlphaWithStencilType == STENCIL_VALUE_UNIFORM) {
|
||||
*uniformMask |= DIRTY_STENCILREPLACEVALUE;
|
||||
@@ -337,10 +344,95 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniform
|
||||
|
||||
if (doTextureProjection) {
|
||||
WRITE(p, " vec4 t = %sProj(tex, %s);\n", texture, texcoord);
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " vec4 t1 = %sProjOffset(tex, %s, ivec2(1, 0));\n", texture, texcoord);
|
||||
WRITE(p, " vec4 t2 = %sProjOffset(tex, %s, ivec2(0, 1));\n", texture, texcoord);
|
||||
WRITE(p, " vec4 t3 = %sProjOffset(tex, %s, ivec2(1, 1));\n", texture, texcoord);
|
||||
}
|
||||
} else {
|
||||
WRITE(p, " vec4 t = %s(tex, %s.xy);\n", texture, texcoord);
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " vec4 t1 = %sOffset(tex, %s.xy, ivec2(1, 0));\n", texture, texcoord);
|
||||
WRITE(p, " vec4 t2 = %sOffset(tex, %s.xy, ivec2(0, 1));\n", texture, texcoord);
|
||||
WRITE(p, " vec4 t3 = %sOffset(tex, %s.xy, ivec2(1, 1));\n", texture, texcoord);
|
||||
}
|
||||
}
|
||||
WRITE(p, " vec4 p = v_color0;\n");
|
||||
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " int depalMask = (u_depal & 0xFF);\n");
|
||||
WRITE(p, " int depalShift = ((u_depal >> 8) & 0xFF);\n");
|
||||
WRITE(p, " int depalOffset = (((u_depal >> 16) & 0xFF) << 4);\n");
|
||||
WRITE(p, " int depalFmt = ((u_depal >> 24) & 0x3);\n");
|
||||
WRITE(p, " bool bilinear = (u_depal >> 31) != 0;\n");
|
||||
WRITE(p, " vec2 fraction = fract(%s.xy * vec2(textureSize(tex, 0).xy));\n", texcoord);
|
||||
WRITE(p, " ivec4 col; int index0; int index1; int index2; int index3;\n");
|
||||
WRITE(p, " switch (depalFmt) {\n"); // We might want to include fmt in the shader ID if this is a performance issue.
|
||||
WRITE(p, " case 0:\n"); // 565
|
||||
WRITE(p, " col = ivec4(t.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index0 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = ivec4(t1.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index1 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t2.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index2 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t3.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index3 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 1:\n"); // 5551
|
||||
WRITE(p, " col = ivec4(t.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index0 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = ivec4(t1.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index1 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t2.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index2 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t3.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index3 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 2:\n"); // 4444
|
||||
WRITE(p, " col = ivec4(t.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index0 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = ivec4(t1.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index1 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t2.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index2 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t3.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index3 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 3:\n"); // 8888
|
||||
WRITE(p, " col = ivec4(t.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index0 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = ivec4(t1.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index1 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t2.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index2 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " col = ivec4(t3.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index3 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " };\n");
|
||||
WRITE(p, " index0 = ((index0 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " t = texelFetch(pal, ivec2(index0, 0), 0);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " index1 = ((index1 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " index2 = ((index2 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " index3 = ((index3 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " t1 = texelFetch(pal, ivec2(index1, 0), 0);\n");
|
||||
WRITE(p, " t2 = texelFetch(pal, ivec2(index2, 0), 0);\n");
|
||||
WRITE(p, " t3 = texelFetch(pal, ivec2(index3, 0), 0);\n");
|
||||
WRITE(p, " t = mix(t, t1, fraction.x);\n");
|
||||
WRITE(p, " t2 = mix(t2, t3, fraction.x);\n");
|
||||
WRITE(p, " t = mix(t, t2, fraction.y);\n");
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
|
||||
if (texFunc != GE_TEXFUNC_REPLACE || !doTextureAlpha)
|
||||
WRITE(p, " vec4 p = v_color0;\n");
|
||||
|
||||
if (doTextureAlpha) { // texfmt == RGBA
|
||||
switch (texFunc) {
|
||||
|
||||
@@ -403,7 +403,7 @@ void FramebufferManagerGLES::MakePixelTexture(const u8 *srcPixels, GEBufferForma
|
||||
render_->FinalizeTexture(drawPixelsTex_, 0, false);
|
||||
|
||||
// TODO: Return instead?
|
||||
render_->BindTexture(0, drawPixelsTex_);
|
||||
render_->BindTexture(TEX_SLOT_PSP_TEXTURE, drawPixelsTex_);
|
||||
}
|
||||
|
||||
void FramebufferManagerGLES::SetViewport2D(int x, int y, int w, int h) {
|
||||
|
||||
@@ -89,6 +89,8 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
// No need to flush before the tex scale/offset commands if we are baking
|
||||
// the tex scale/offset into the vertices anyway.
|
||||
|
||||
UpdateCmdInfo();
|
||||
|
||||
BuildReportingInfo();
|
||||
// Update again after init to be sure of any silly driver problems.
|
||||
UpdateVsyncInterval(true);
|
||||
@@ -344,6 +346,7 @@ void GPU_GLES::DeviceRestore() {
|
||||
draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext();
|
||||
ILOG("GPU_GLES: DeviceRestore");
|
||||
|
||||
UpdateCmdInfo();
|
||||
UpdateVsyncInterval(true);
|
||||
|
||||
textureCacheGL_->DeviceRestore(draw_);
|
||||
@@ -363,6 +366,7 @@ void GPU_GLES::InitClear() {
|
||||
|
||||
void GPU_GLES::BeginHostFrame() {
|
||||
GPUCommon::BeginHostFrame();
|
||||
UpdateCmdInfo();
|
||||
if (resized_) {
|
||||
CheckGPUFeatures();
|
||||
framebufferManager_->Resized();
|
||||
|
||||
@@ -80,6 +80,8 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
semantics.push_back({ ATTR_POSITION, "position" });
|
||||
semantics.push_back({ ATTR_TEXCOORD, "texcoord" });
|
||||
semantics.push_back({ ATTR_NORMAL, "normal" });
|
||||
semantics.push_back({ ATTR_W1, "w1" });
|
||||
semantics.push_back({ ATTR_W2, "w2" });
|
||||
semantics.push_back({ ATTR_COLOR0, "color0" });
|
||||
semantics.push_back({ ATTR_COLOR1, "color1" });
|
||||
|
||||
@@ -102,14 +104,28 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
queries.push_back({ &u_blendFixA, "u_blendFixA" });
|
||||
queries.push_back({ &u_blendFixB, "u_blendFixB" });
|
||||
queries.push_back({ &u_fbotexSize, "u_fbotexSize" });
|
||||
queries.push_back({ &u_pal, "pal" });
|
||||
|
||||
// Transform
|
||||
queries.push_back({ &u_view, "u_view" });
|
||||
queries.push_back({ &u_world, "u_world" });
|
||||
queries.push_back({ &u_texmtx, "u_texmtx" });
|
||||
|
||||
if (VSID.Bit(VS_BIT_ENABLE_BONES))
|
||||
numBones = TranslateNumBones(VSID.Bits(VS_BIT_BONES, 3) + 1);
|
||||
else
|
||||
numBones = 0;
|
||||
queries.push_back({ &u_depthRange, "u_depthRange" });
|
||||
|
||||
#ifdef USE_BONE_ARRAY
|
||||
queries.push_back({ &u_bone, "u_bone" });
|
||||
#else
|
||||
static const char * const boneNames[8] = { "u_bone0", "u_bone1", "u_bone2", "u_bone3", "u_bone4", "u_bone5", "u_bone6", "u_bone7", };
|
||||
for (int i = 0; i < 8; i++) {
|
||||
queries.push_back({ &u_bone[i], boneNames[i] });
|
||||
}
|
||||
#endif
|
||||
|
||||
// Lighting, texturing
|
||||
queries.push_back({ &u_ambient, "u_ambient" });
|
||||
queries.push_back({ &u_matambientalpha, "u_matambientalpha" });
|
||||
@@ -148,6 +164,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
queries.push_back({ &u_spline_count_v, "u_spline_count_v" });
|
||||
queries.push_back({ &u_spline_type_u, "u_spline_type_u" });
|
||||
queries.push_back({ &u_spline_type_v, "u_spline_type_v" });
|
||||
queries.push_back({ &u_depal, "u_depal" });
|
||||
|
||||
attrMask = vs->GetAttrMask();
|
||||
availableUniforms = vs->GetUniformMask() | fs->GetUniformMask();
|
||||
@@ -156,6 +173,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
initialize.push_back({ &u_tex, 0, 0 });
|
||||
initialize.push_back({ &u_fbotex, 0, 1 });
|
||||
initialize.push_back({ &u_testtex, 0, 2 });
|
||||
initialize.push_back({ &u_pal, 0, 3 }); // CLUT
|
||||
initialize.push_back({ &u_tess_pos_tex, 0, 4 }); // Texture unit 4
|
||||
initialize.push_back({ &u_tess_tex_tex, 0, 5 }); // Texture unit 5
|
||||
initialize.push_back({ &u_tess_col_tex, 0, 6 }); // Texture unit 6
|
||||
@@ -270,6 +288,17 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid) {
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
if (dirty & DIRTY_DEPAL) {
|
||||
int indexMask = gstate.getClutIndexMask();
|
||||
int indexShift = gstate.getClutIndexShift();
|
||||
int indexOffset = gstate.getClutIndexStartPos() >> 4;
|
||||
int format = gstate_c.depalFramebufferFormat;
|
||||
uint32_t val = BytesToUint32(indexMask, indexShift, indexOffset, format);
|
||||
// Poke in a bilinear filter flag in the top bit.
|
||||
val |= gstate.isMagnifyFilteringEnabled() << 31;
|
||||
render_->SetUniformI1(&u_depal, val);
|
||||
}
|
||||
|
||||
// Update any dirty uniforms before we draw
|
||||
if (dirty & DIRTY_PROJMATRIX) {
|
||||
Matrix4x4 flippedMatrix;
|
||||
@@ -472,6 +501,13 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid) {
|
||||
float f = (float)gstate.getStencilTestRef() * (1.0f / 255.0f);
|
||||
render_->SetUniformF(&u_stencilReplaceValue, 1, &f);
|
||||
}
|
||||
float bonetemp[16];
|
||||
for (int i = 0; i < numBones; i++) {
|
||||
if (dirty & (DIRTY_BONEMATRIX0 << i)) {
|
||||
ConvertMatrix4x3To4x4(bonetemp, gstate.boneMatrix + 12 * i);
|
||||
render_->SetUniformM4x4(&u_bone[i], bonetemp);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty & DIRTY_SHADERBLEND) {
|
||||
if (u_blendFixA != -1) {
|
||||
@@ -797,7 +833,7 @@ std::string ShaderManagerGLES::DebugGetShaderString(std::string id, DebugShaderT
|
||||
// as sometimes these features might have an effect on the ID bits.
|
||||
|
||||
#define CACHE_HEADER_MAGIC 0x83277592
|
||||
#define CACHE_VERSION 7
|
||||
#define CACHE_VERSION 12
|
||||
struct CacheHeader {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
|
||||
@@ -34,8 +34,10 @@ enum {
|
||||
ATTR_POSITION = 0,
|
||||
ATTR_TEXCOORD = 1,
|
||||
ATTR_NORMAL = 2,
|
||||
ATTR_COLOR0 = 3,
|
||||
ATTR_COLOR1 = 4,
|
||||
ATTR_W1 = 3,
|
||||
ATTR_W2 = 4,
|
||||
ATTR_COLOR0 = 5,
|
||||
ATTR_COLOR1 = 6,
|
||||
|
||||
ATTR_COUNT,
|
||||
};
|
||||
@@ -70,12 +72,23 @@ public:
|
||||
int u_world;
|
||||
int u_depthRange; // x,y = viewport xscale/xcenter. z,w=clipping minz/maxz (?)
|
||||
|
||||
#ifdef USE_BONE_ARRAY
|
||||
int u_bone; // array, size is numBones
|
||||
#else
|
||||
int u_bone[8];
|
||||
#endif
|
||||
int numBones;
|
||||
|
||||
// Shader blending.
|
||||
int u_fbotex;
|
||||
int u_blendFixA;
|
||||
int u_blendFixB;
|
||||
int u_fbotexSize;
|
||||
|
||||
// Shader depal
|
||||
int u_pal; // the texture
|
||||
int u_depal; // the params
|
||||
|
||||
// Fragment processing inputs
|
||||
int u_alphacolorref;
|
||||
int u_alphacolormask;
|
||||
|
||||
@@ -124,7 +124,7 @@ static const GLushort logicOps[] = {
|
||||
inline void DrawEngineGLES::ResetShaderBlending() {
|
||||
if (fboTexBound_) {
|
||||
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
||||
renderManager->BindTexture(1, nullptr);
|
||||
renderManager->BindTexture(TEX_SLOT_SHADERBLEND_SRC, nullptr);
|
||||
fboTexBound_ = false;
|
||||
}
|
||||
}
|
||||
@@ -331,7 +331,7 @@ void DrawEngineGLES::ApplyDrawStateLate(bool setStencil, int stencilValue) {
|
||||
if (!gstate.isModeClear()) {
|
||||
// Apply last, once we know the alpha params of the texture.
|
||||
if (gstate.isAlphaTestEnabled() || gstate.isColorTestEnabled()) {
|
||||
fragmentTestCache_->BindTestTexture(2);
|
||||
fragmentTestCache_->BindTestTexture(TEX_SLOT_ALPHATEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ void TextureCacheGLES::UpdateSamplingParams(TexCacheEntry &entry, bool force) {
|
||||
render_->SetTextureSampler(0, sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT, MagFiltGL[magFilt], MinFiltGL[minFilt], aniso);
|
||||
}
|
||||
|
||||
void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) {
|
||||
void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, bool forcePoint) {
|
||||
int minFilt;
|
||||
int magFilt;
|
||||
bool sClamp;
|
||||
@@ -171,6 +171,10 @@ void TextureCacheGLES::SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferH
|
||||
GetSamplingParams(minFilt, magFilt, sClamp, tClamp, lodBias, 0, 0, mode);
|
||||
|
||||
minFilt &= 1; // framebuffers can't mipmap.
|
||||
if (forcePoint) {
|
||||
minFilt &= ~1;
|
||||
magFilt &= ~1;
|
||||
}
|
||||
|
||||
// Often the framebuffer will not match the texture size. We'll wrap/clamp in the shader in that case.
|
||||
// This happens whether we have OES_texture_npot or not.
|
||||
@@ -324,10 +328,11 @@ void TextureCacheGLES::BindTexture(TexCacheEntry *entry) {
|
||||
lastBoundTexture = entry->textureName;
|
||||
}
|
||||
UpdateSamplingParams(*entry, false);
|
||||
gstate_c.useShaderDepal = false;
|
||||
}
|
||||
|
||||
void TextureCacheGLES::Unbind() {
|
||||
render_->BindTexture(0, nullptr);
|
||||
render_->BindTexture(TEX_SLOT_PSP_TEXTURE, nullptr);
|
||||
InvalidateLastTexture();
|
||||
}
|
||||
|
||||
@@ -434,7 +439,33 @@ protected:
|
||||
void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) {
|
||||
DepalShader *depal = nullptr;
|
||||
uint32_t clutMode = gstate.clutformat & 0xFFFFFF;
|
||||
|
||||
#if 0
|
||||
bool useShaderDepal = gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300);
|
||||
#else
|
||||
bool useShaderDepal = false;
|
||||
#endif
|
||||
|
||||
if ((entry->status & TexCacheEntry::STATUS_DEPALETTIZE) && !g_Config.bDisableSlowFramebufEffects) {
|
||||
if (useShaderDepal) {
|
||||
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
|
||||
GLRTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_);
|
||||
render_->BindTexture(TEX_SLOT_CLUT, clutTexture);
|
||||
framebufferManagerGL_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET);
|
||||
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, true);
|
||||
InvalidateLastTexture();
|
||||
|
||||
// Since we started/ended render passes, might need these.
|
||||
gstate_c.Dirty(DIRTY_DEPAL);
|
||||
gstate_c.useShaderDepal = true;
|
||||
gstate_c.depalFramebufferFormat = framebuffer->drawnFormat;
|
||||
const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16);
|
||||
const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor;
|
||||
TexCacheEntry::TexStatus alphaStatus = CheckAlpha((const uint8_t *)clutBuf_, getClutDestFormat(clutFormat), clutTotalColors, clutTotalColors, 1);
|
||||
gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL);
|
||||
return;
|
||||
}
|
||||
|
||||
depal = depalShaderCache_->GetDepalettizeShader(clutMode, framebuffer->drawnFormat);
|
||||
}
|
||||
if (depal) {
|
||||
@@ -451,8 +482,8 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram
|
||||
shaderApply.Use(render_, drawEngine_, shadeInputLayout_);
|
||||
|
||||
framebufferManagerGL_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY);
|
||||
render_->BindTexture(3, clutTexture);
|
||||
render_->SetTextureSampler(3, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f);
|
||||
render_->BindTexture(TEX_SLOT_CLUT, clutTexture);
|
||||
render_->SetTextureSampler(TEX_SLOT_CLUT, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_NEAREST, GL_NEAREST, 0.0f);
|
||||
|
||||
shaderApply.Shade(render_);
|
||||
|
||||
@@ -472,7 +503,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram
|
||||
}
|
||||
|
||||
framebufferManagerGL_->RebindFramebuffer();
|
||||
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight);
|
||||
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, false);
|
||||
|
||||
InvalidateLastTexture();
|
||||
|
||||
@@ -669,7 +700,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
|
||||
|
||||
// This will rebind it, but that's okay.
|
||||
// Need to actually bind it now - it might only have gotten bound in the init phase.
|
||||
render_->BindTexture(0, entry->textureName);
|
||||
render_->BindTexture(TEX_SLOT_PSP_TEXTURE, entry->textureName);
|
||||
UpdateSamplingParams(*entry, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight);
|
||||
void SetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight, bool forcePoint);
|
||||
bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) override;
|
||||
|
||||
void DeviceLost();
|
||||
|
||||
@@ -39,6 +39,30 @@
|
||||
|
||||
#define WRITE p+=sprintf
|
||||
|
||||
static const char * const boneWeightAttrDecl[9] = {
|
||||
"#ERROR#",
|
||||
"attribute mediump float w1;\n",
|
||||
"attribute mediump vec2 w1;\n",
|
||||
"attribute mediump vec3 w1;\n",
|
||||
"attribute mediump vec4 w1;\n",
|
||||
"attribute mediump vec4 w1;\nattribute mediump float w2;\n",
|
||||
"attribute mediump vec4 w1;\nattribute mediump vec2 w2;\n",
|
||||
"attribute mediump vec4 w1;\nattribute mediump vec3 w2;\n",
|
||||
"attribute mediump vec4 w1, w2;\n",
|
||||
};
|
||||
|
||||
static const char * const boneWeightInDecl[9] = {
|
||||
"#ERROR#",
|
||||
"in mediump float w1;\n",
|
||||
"in mediump vec2 w1;\n",
|
||||
"in mediump vec3 w1;\n",
|
||||
"in mediump vec4 w1;\n",
|
||||
"in mediump vec4 w1;\nin mediump float w2;\n",
|
||||
"in mediump vec4 w1;\nin mediump vec2 w2;\n",
|
||||
"in mediump vec4 w1;\nin mediump vec3 w2;\n",
|
||||
"in mediump vec4 w1, w2;\n",
|
||||
};
|
||||
|
||||
enum DoLightComputation {
|
||||
LIGHT_OFF,
|
||||
LIGHT_SHADE,
|
||||
@@ -89,6 +113,7 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
bool glslES30 = false;
|
||||
const char *varying = "varying";
|
||||
const char *attribute = "attribute";
|
||||
const char * const * boneWeightDecl = boneWeightAttrDecl;
|
||||
const char *texelFetch = NULL;
|
||||
bool highpFog = false;
|
||||
bool highpTexcoord = false;
|
||||
@@ -141,6 +166,7 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
if (glslES30 || gl_extensions.IsCoreContext) {
|
||||
attribute = "in";
|
||||
varying = "out";
|
||||
boneWeightDecl = boneWeightInDecl;
|
||||
}
|
||||
|
||||
bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH);
|
||||
@@ -164,6 +190,7 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
bool flipNormal = id.Bit(VS_BIT_NORM_REVERSE);
|
||||
int ls0 = id.Bits(VS_BIT_LS0, 2);
|
||||
int ls1 = id.Bits(VS_BIT_LS1, 2);
|
||||
bool enableBones = id.Bit(VS_BIT_ENABLE_BONES);
|
||||
bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE);
|
||||
int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3);
|
||||
|
||||
@@ -189,6 +216,16 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
}
|
||||
}
|
||||
|
||||
int numBoneWeights = 0;
|
||||
int boneWeightScale = id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2);
|
||||
if (enableBones) {
|
||||
numBoneWeights = 1 + id.Bits(VS_BIT_BONES, 3);
|
||||
WRITE(p, "%s", boneWeightDecl[numBoneWeights]);
|
||||
*attrMask |= 1 << ATTR_W1;
|
||||
if (numBoneWeights >= 5)
|
||||
*attrMask |= 1 << ATTR_W2;
|
||||
}
|
||||
|
||||
if (useHWTransform)
|
||||
WRITE(p, "%s vec3 position;\n", attribute);
|
||||
else
|
||||
@@ -241,6 +278,17 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
WRITE(p, "uniform mediump mat4 u_texmtx;\n");
|
||||
*uniformMask |= DIRTY_TEXMATRIX;
|
||||
}
|
||||
if (enableBones) {
|
||||
#ifdef USE_BONE_ARRAY
|
||||
WRITE(p, "uniform mediump mat4 u_bone[%i];\n", numBoneWeights);
|
||||
*uniformMask |= DIRTY_BONE_UNIFORMS;
|
||||
#else
|
||||
for (int i = 0; i < numBoneWeights; i++) {
|
||||
WRITE(p, "uniform mat4 u_bone%i;\n", i);
|
||||
*uniformMask |= DIRTY_BONEMATRIX0 << i;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (doTexture) {
|
||||
WRITE(p, "uniform vec4 u_uvscaleoffset;\n");
|
||||
*uniformMask |= DIRTY_UVSCALEOFFSET;
|
||||
@@ -451,111 +499,189 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask,
|
||||
WriteGuardBand(p);
|
||||
WRITE(p, " gl_Position = outPos;\n");
|
||||
} else {
|
||||
// Step 1: World Transform
|
||||
// Hardware tessellation
|
||||
if (doBezier || doSpline) {
|
||||
WRITE(p, " vec3 _pos[16];\n");
|
||||
WRITE(p, " vec2 _tex[16];\n");
|
||||
WRITE(p, " vec4 _col[16];\n");
|
||||
WRITE(p, " int num_patches_u = %s;\n", doBezier ? "(u_spline_count_u - 1) / 3" : "u_spline_count_u - 3");
|
||||
WRITE(p, " int u = int(mod(float(gl_InstanceID), float(num_patches_u)));\n");
|
||||
WRITE(p, " int v = gl_InstanceID / num_patches_u;\n");
|
||||
WRITE(p, " ivec2 patch_pos = ivec2(u, v);\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " for (int j = 0; j < 4; j++) {\n");
|
||||
WRITE(p, " int index = (i + v%s) * u_spline_count_u + (j + u%s);\n", doBezier ? " * 3" : "", doBezier ? " * 3" : "");
|
||||
WRITE(p, " _pos[i * 4 + j] = %s(u_tess_pos_tex, ivec2(index, 0), 0).xyz;\n", texelFetch);
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[i * 4 + j] = %s(u_tess_tex_tex, ivec2(index, 0), 0).xy;\n", texelFetch);
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[i * 4 + j] = %s(u_tess_col_tex, ivec2(index, 0), 0).rgba;\n", texelFetch);
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " vec2 tess_pos = position.xy;\n");
|
||||
WRITE(p, " vec2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1.0 - tess_pos) * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3.0 * tess_pos * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3.0 * tess_pos * tess_pos * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " ivec2 spline_num_patches = ivec2(u_spline_count_u - 3, u_spline_count_v - 3);\n");
|
||||
WRITE(p, " ivec2 spline_type = ivec2(u_spline_type_u, u_spline_type_v);\n");
|
||||
WRITE(p, " vec2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + vec2(patch_pos), knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " vec3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " vec2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec2 tex = tess_pos + vec2(patch_pos);\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " vec4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec4 col = %s(u_tess_col_tex, ivec2(0, 0), 0).rgba;\n", texelFetch);
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
// Step 1: World Transform / Skinning
|
||||
if (!enableBones) {
|
||||
// Hardware tessellation
|
||||
if (doBezier || doSpline) {
|
||||
WRITE(p, " vec3 _pos[16];\n");
|
||||
WRITE(p, " vec2 _tex[16];\n");
|
||||
WRITE(p, " vec4 _col[16];\n");
|
||||
WRITE(p, " int num_patches_u = %s;\n", doBezier ? "(u_spline_count_u - 1) / 3" : "u_spline_count_u - 3");
|
||||
WRITE(p, " int u = int(mod(float(gl_InstanceID), float(num_patches_u)));\n");
|
||||
WRITE(p, " int v = gl_InstanceID / num_patches_u;\n");
|
||||
WRITE(p, " ivec2 patch_pos = ivec2(u, v);\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " for (int j = 0; j < 4; j++) {\n");
|
||||
WRITE(p, " int index = (i + v%s) * u_spline_count_u + (j + u%s);\n", doBezier ? " * 3" : "", doBezier ? " * 3" : "");
|
||||
WRITE(p, " _pos[i * 4 + j] = %s(u_tess_pos_tex, ivec2(index, 0), 0).xyz;\n", texelFetch);
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[i * 4 + j] = %s(u_tess_tex_tex, ivec2(index, 0), 0).xy;\n", texelFetch);
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[i * 4 + j] = %s(u_tess_col_tex, ivec2(index, 0), 0).rgba;\n", texelFetch);
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " vec2 tess_pos = position.xy;\n");
|
||||
WRITE(p, " vec2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " vec2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3.0 * (tess_pos - 1.0) * (tess_pos - 1.0); \n");
|
||||
WRITE(p, " bernderiv[1] = 9.0 * tess_pos * tess_pos - 12.0 * tess_pos + 3.0; \n");
|
||||
WRITE(p, " bernderiv[2] = 3.0 * (2.0 - 3.0 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3.0 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " vec2 bernderiv_u[4];\n");
|
||||
WRITE(p, " vec2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = vec2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = vec2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " vec3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " vec3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1.0 - tess_pos) * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3.0 * tess_pos * (1.0 - tess_pos) * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3.0 * tess_pos * tess_pos * (1.0 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " vec2 tess_next_u = vec2(normal.x, 0.0);\n");
|
||||
WRITE(p, " vec2 tess_next_v = vec2(0.0, normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " vec2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " vec2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " vec2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " vec2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_u = tess_sample(_pos, weights);\n");
|
||||
WRITE(p, " ivec2 spline_num_patches = ivec2(u_spline_count_u - 3, u_spline_count_v - 3);\n");
|
||||
WRITE(p, " ivec2 spline_type = ivec2(u_spline_type_u, u_spline_type_v);\n");
|
||||
WRITE(p, " vec2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + vec2(patch_pos), knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " vec3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " vec2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec2 tex = tess_pos + vec2(patch_pos);\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " vec4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec4 col = %s(u_tess_col_tex, ivec2(0, 0), 0).rgba;\n", texelFetch);
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " vec2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3.0 * (tess_pos - 1.0) * (tess_pos - 1.0); \n");
|
||||
WRITE(p, " bernderiv[1] = 9.0 * tess_pos * tess_pos - 12.0 * tess_pos + 3.0; \n");
|
||||
WRITE(p, " bernderiv[2] = 3.0 * (2.0 - 3.0 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3.0 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " vec3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " vec3 dv = pos_d - pos_u;\n");
|
||||
WRITE(p, " vec2 bernderiv_u[4];\n");
|
||||
WRITE(p, " vec2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = vec2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = vec2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " vec3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " vec3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " vec2 tess_next_u = vec2(normal.x, 0.0);\n");
|
||||
WRITE(p, " vec2 tess_next_v = vec2(0.0, normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " vec2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " vec2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " vec2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " vec2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + vec2(patch_pos), knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_u = tess_sample(_pos, weights);\n");
|
||||
|
||||
WRITE(p, " vec3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " vec3 dv = pos_d - pos_u;\n");
|
||||
}
|
||||
WRITE(p, " vec3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
}
|
||||
WRITE(p, " vec3 worldpos = (u_world * vec4(pos.xyz, 1.0)).xyz;\n");
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(%snrm, 0.0)).xyz);\n", flipNormalTess ? "-" : "");
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
}
|
||||
WRITE(p, " vec3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
}
|
||||
WRITE(p, " vec3 worldpos = (u_world * vec4(pos.xyz, 1.0)).xyz;\n");
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(%snrm, 0.0)).xyz);\n", flipNormalTess ? "-" : "");
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
// No skinning, just standard T&L.
|
||||
WRITE(p, " vec3 worldpos = (u_world * vec4(position.xyz, 1.0)).xyz;\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(%snormal, 0.0)).xyz);\n", flipNormal ? "-" : "");
|
||||
else
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else {
|
||||
WRITE(p, " vec3 worldpos = (u_world * vec4(position.xyz, 1.0)).xyz;\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(%snormal, 0.0)).xyz);\n", flipNormal ? "-" : "");
|
||||
static const char *rescale[4] = {"", " * 1.9921875", " * 1.999969482421875", ""}; // 2*127.5f/128.f, 2*32767.5f/32768.f, 1.0f};
|
||||
const char *factor = rescale[boneWeightScale];
|
||||
|
||||
static const char * const boneWeightAttr[8] = {
|
||||
"w1.x", "w1.y", "w1.z", "w1.w",
|
||||
"w2.x", "w2.y", "w2.z", "w2.w",
|
||||
};
|
||||
|
||||
#if defined(USE_FOR_LOOP) && defined(USE_BONE_ARRAY)
|
||||
|
||||
// To loop through the weights, we unfortunately need to put them in a float array.
|
||||
// GLSL ES sucks - no way to directly initialize an array!
|
||||
switch (numBoneWeights) {
|
||||
case 1: WRITE(p, " float w[1]; w[0] = w1;\n"); break;
|
||||
case 2: WRITE(p, " float w[2]; w[0] = w1.x; w[1] = w1.y;\n"); break;
|
||||
case 3: WRITE(p, " float w[3]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z;\n"); break;
|
||||
case 4: WRITE(p, " float w[4]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z; w[3] = w1.w;\n"); break;
|
||||
case 5: WRITE(p, " float w[5]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z; w[3] = w1.w; w[4] = w2;\n"); break;
|
||||
case 6: WRITE(p, " float w[6]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z; w[3] = w1.w; w[4] = w2.x; w[5] = w2.y;\n"); break;
|
||||
case 7: WRITE(p, " float w[7]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z; w[3] = w1.w; w[4] = w2.x; w[5] = w2.y; w[6] = w2.z;\n"); break;
|
||||
case 8: WRITE(p, " float w[8]; w[0] = w1.x; w[1] = w1.y; w[2] = w1.z; w[3] = w1.w; w[4] = w2.x; w[5] = w2.y; w[6] = w2.z; w[7] = w2.w;\n"); break;
|
||||
}
|
||||
|
||||
WRITE(p, " mat4 skinMatrix = w[0] * u_bone[0];\n");
|
||||
if (numBoneWeights > 1) {
|
||||
WRITE(p, " for (int i = 1; i < %i; i++) {\n", numBoneWeights);
|
||||
WRITE(p, " skinMatrix += w[i] * u_bone[i];\n");
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef USE_BONE_ARRAY
|
||||
if (numBoneWeights == 1)
|
||||
WRITE(p, " mat4 skinMatrix = w1 * u_bone[0]");
|
||||
else
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
WRITE(p, " mat4 skinMatrix = w1.x * u_bone[0]");
|
||||
for (int i = 1; i < numBoneWeights; i++) {
|
||||
const char *weightAttr = boneWeightAttr[i];
|
||||
// workaround for "cant do .x of scalar" issue
|
||||
if (numBoneWeights == 1 && i == 0) weightAttr = "w1";
|
||||
if (numBoneWeights == 5 && i == 4) weightAttr = "w2";
|
||||
WRITE(p, " + %s * u_bone[%i]", weightAttr, i);
|
||||
}
|
||||
#else
|
||||
// Uncomment this to screw up bone shaders to check the vertex shader software fallback
|
||||
// WRITE(p, "THIS SHOULD ERROR! #error");
|
||||
if (numBoneWeights == 1)
|
||||
WRITE(p, " mat4 skinMatrix = w1 * u_bone0");
|
||||
else
|
||||
WRITE(p, " mat4 skinMatrix = w1.x * u_bone0");
|
||||
for (int i = 1; i < numBoneWeights; i++) {
|
||||
const char *weightAttr = boneWeightAttr[i];
|
||||
// workaround for "cant do .x of scalar" issue
|
||||
if (numBoneWeights == 1 && i == 0) weightAttr = "w1";
|
||||
if (numBoneWeights == 5 && i == 4) weightAttr = "w2";
|
||||
WRITE(p, " + %s * u_bone%i", weightAttr, i);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
WRITE(p, ";\n");
|
||||
|
||||
// Trying to simplify this results in bugs in LBP...
|
||||
WRITE(p, " vec3 skinnedpos = (skinMatrix * vec4(position, 1.0)).xyz %s;\n", factor);
|
||||
WRITE(p, " vec3 worldpos = (u_world * vec4(skinnedpos, 1.0)).xyz;\n");
|
||||
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 skinnednormal = (skinMatrix * vec4(%snormal, 0.0)).xyz %s;\n", flipNormal ? "-" : "", factor);
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 skinnednormal = (skinMatrix * vec4(0.0, 0.0, %s1.0, 0.0)).xyz %s;\n", flipNormal ? "-" : "", factor);
|
||||
}
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(skinnednormal, 0.0)).xyz);\n");
|
||||
}
|
||||
|
||||
WRITE(p, " vec4 viewPos = u_view * vec4(worldpos, 1.0);\n");
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// #define USE_BONE_ARRAY
|
||||
|
||||
struct VShaderID;
|
||||
|
||||
void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, uint64_t *uniformMask);
|
||||
+92
-9
@@ -46,8 +46,8 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
{ GE_CMD_BEZIER, FLAG_FLUSHBEFORE | FLAG_EXECUTE, 0, &GPUCommon::Execute_Bezier },
|
||||
{ GE_CMD_SPLINE, FLAG_FLUSHBEFORE | FLAG_EXECUTE, 0, &GPUCommon::Execute_Spline },
|
||||
|
||||
// Changing the vertex type does not always require us to flush so handle that in Execute_VertexType.
|
||||
{ GE_CMD_VERTEXTYPE, FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType },
|
||||
// Changing the vertex type requires us to flush.
|
||||
{ GE_CMD_VERTEXTYPE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_VertexType },
|
||||
|
||||
{ GE_CMD_LOADCLUT, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTE, 0, &GPUCommon::Execute_LoadClut },
|
||||
|
||||
@@ -160,7 +160,7 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
// These must flush on change, so that LoadClut doesn't have to always flush.
|
||||
{ GE_CMD_CLUTADDR, FLAG_FLUSHBEFOREONCHANGE },
|
||||
{ GE_CMD_CLUTADDRUPPER, FLAG_FLUSHBEFOREONCHANGE },
|
||||
{ GE_CMD_CLUTFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS },
|
||||
{ GE_CMD_CLUTFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_DEPAL },
|
||||
|
||||
// Morph weights. TODO: Remove precomputation?
|
||||
{ GE_CMD_MORPHWEIGHT0, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GPUCommon::Execute_MorphWeight },
|
||||
@@ -403,11 +403,23 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) :
|
||||
ERROR_LOG(G3D, "Command missing from table: %02x (%i)", i, i);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCmdInfo();
|
||||
}
|
||||
|
||||
GPUCommon::~GPUCommon() {
|
||||
}
|
||||
|
||||
void GPUCommon::UpdateCmdInfo() {
|
||||
if (g_Config.bSoftwareSkinning) {
|
||||
cmdInfo_[GE_CMD_VERTEXTYPE].flags &= ~FLAG_FLUSHBEFOREONCHANGE;
|
||||
cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexTypeSkinning;
|
||||
} else {
|
||||
cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE;
|
||||
cmdInfo_[GE_CMD_VERTEXTYPE].func = &GPUCommon::Execute_VertexType;
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommon::BeginHostFrame() {
|
||||
ReapplyGfxState();
|
||||
|
||||
@@ -1421,12 +1433,22 @@ void GPUCommon::Execute_TexSize0(u32 op, u32 diff) {
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommon::Execute_VertexType(u32 op, u32 diff) {
|
||||
if (diff)
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
|
||||
if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) {
|
||||
gstate_c.Dirty(DIRTY_UVSCALEOFFSET);
|
||||
if (diff & GE_VTYPE_THROUGH_MASK)
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommon::Execute_LoadClut(u32 op, u32 diff) {
|
||||
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
|
||||
textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes());
|
||||
}
|
||||
|
||||
void GPUCommon::Execute_VertexType(u32 op, u32 diff) {
|
||||
void GPUCommon::Execute_VertexTypeSkinning(u32 op, u32 diff) {
|
||||
// Don't flush when weight count changes.
|
||||
if (diff & ~GE_VTYPE_WEIGHTCOUNT_MASK) {
|
||||
// Restore and flush
|
||||
@@ -1435,6 +1457,12 @@ void GPUCommon::Execute_VertexType(u32 op, u32 diff) {
|
||||
gstate.vertType ^= diff;
|
||||
if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK))
|
||||
gstate_c.Dirty(DIRTY_UVSCALEOFFSET);
|
||||
// In this case, we may be doing weights and morphs.
|
||||
// Update any bone matrix uniforms so it uses them correctly.
|
||||
if ((op & GE_VTYPE_MORPHCOUNT_MASK) != 0) {
|
||||
gstate_c.Dirty(gstate_c.deferredVertTypeDirty);
|
||||
gstate_c.deferredVertTypeDirty = 0;
|
||||
}
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
|
||||
}
|
||||
if (diff & GE_VTYPE_THROUGH_MASK)
|
||||
@@ -1527,6 +1555,11 @@ void GPUCommon::Execute_Prim(u32 op, u32 diff) {
|
||||
// PRIM commands with other commands. A special case that might be interesting is that game
|
||||
// that changes culling mode between each prim, we could just change the triangle winding
|
||||
// right here to still be able to join draw calls.
|
||||
|
||||
uint32_t vtypeCheckMask = ~GE_VTYPE_WEIGHTCOUNT_MASK;
|
||||
if (!g_Config.bSoftwareSkinning)
|
||||
vtypeCheckMask = 0xFFFFFFFF;
|
||||
|
||||
#ifndef MOBILE_DEVICE
|
||||
if (debugRecording_ || host->GPUDebuggingActive())
|
||||
goto bail;
|
||||
@@ -1560,7 +1593,7 @@ void GPUCommon::Execute_Prim(u32 op, u32 diff) {
|
||||
{
|
||||
uint32_t diff = data ^ vertexType;
|
||||
// don't mask upper bits, vertexType is unmasked
|
||||
if (diff & ~GE_VTYPE_WEIGHTCOUNT_MASK) {
|
||||
if (diff & vtypeCheckMask) {
|
||||
goto bail;
|
||||
} else {
|
||||
vertexType = data;
|
||||
@@ -1662,6 +1695,10 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) {
|
||||
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
|
||||
}
|
||||
|
||||
if (vertTypeIsSkinningEnabled(gstate.vertType)) {
|
||||
DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType));
|
||||
}
|
||||
|
||||
GEPatchPrimType patchPrim = gstate.getPatchPrimitiveType();
|
||||
SetDrawType(DRAW_BEZIER, PatchPrimToPrim(patchPrim));
|
||||
|
||||
@@ -1720,6 +1757,10 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) {
|
||||
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
|
||||
}
|
||||
|
||||
if (vertTypeIsSkinningEnabled(gstate.vertType)) {
|
||||
DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType));
|
||||
}
|
||||
|
||||
int sp_ucount = op & 0xFF;
|
||||
int sp_vcount = (op >> 8) & 0xFF;
|
||||
int sp_utype = (op >> 16) & 0x3;
|
||||
@@ -2000,10 +2041,34 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) {
|
||||
}
|
||||
|
||||
if (fastLoad) {
|
||||
while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) {
|
||||
dst[i] = src[i] << 8;
|
||||
if (++i >= end) {
|
||||
break;
|
||||
// If we can't use software skinning, we have to flush and dirty.
|
||||
if (!g_Config.bSoftwareSkinning) {
|
||||
while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) {
|
||||
const u32 newVal = src[i] << 8;
|
||||
if (dst[i] != newVal) {
|
||||
Flush();
|
||||
dst[i] = newVal;
|
||||
}
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int numPlusCount = (op & 0x7F) + i;
|
||||
for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) {
|
||||
gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12));
|
||||
}
|
||||
} else {
|
||||
while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) {
|
||||
dst[i] = src[i] << 8;
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int numPlusCount = (op & 0x7F) + i;
|
||||
for (unsigned int num = op & 0x7F; num < numPlusCount; num += 12) {
|
||||
gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2021,6 +2086,13 @@ void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) {
|
||||
int num = gstate.boneMatrixNumber & 0x7F;
|
||||
u32 newVal = op << 8;
|
||||
if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) {
|
||||
// Bone matrices should NOT flush when software skinning is enabled!
|
||||
if (!g_Config.bSoftwareSkinning) {
|
||||
Flush();
|
||||
gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12));
|
||||
} else {
|
||||
gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12);
|
||||
}
|
||||
((u32 *)gstate.boneMatrix)[num] = newVal;
|
||||
}
|
||||
num++;
|
||||
@@ -2161,6 +2233,17 @@ void GPUCommon::Execute_Unknown(u32 op, u32 diff) {
|
||||
void GPUCommon::FastLoadBoneMatrix(u32 target) {
|
||||
const int num = gstate.boneMatrixNumber & 0x7F;
|
||||
const int mtxNum = num / 12;
|
||||
uint32_t uniformsToDirty = DIRTY_BONEMATRIX0 << mtxNum;
|
||||
if ((num - 12 * mtxNum) != 0) {
|
||||
uniformsToDirty |= DIRTY_BONEMATRIX0 << ((mtxNum + 1) & 7);
|
||||
}
|
||||
|
||||
if (!g_Config.bSoftwareSkinning) {
|
||||
Flush();
|
||||
gstate_c.Dirty(uniformsToDirty);
|
||||
} else {
|
||||
gstate_c.deferredVertTypeDirty |= uniformsToDirty;
|
||||
}
|
||||
gstate.FastLoadBoneMatrix(target);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ public:
|
||||
}
|
||||
virtual void CheckGPUFeatures() = 0;
|
||||
|
||||
void UpdateCmdInfo();
|
||||
|
||||
bool IsReady() override {
|
||||
return true;
|
||||
}
|
||||
@@ -129,6 +131,7 @@ public:
|
||||
void Execute_End(u32 op, u32 diff);
|
||||
|
||||
void Execute_VertexType(u32 op, u32 diff);
|
||||
void Execute_VertexTypeSkinning(u32 op, u32 diff);
|
||||
|
||||
void Execute_Prim(u32 op, u32 diff);
|
||||
void Execute_Bezier(u32 op, u32 diff);
|
||||
|
||||
@@ -240,6 +240,13 @@ void GPUgstate::Restore(u32_le *ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
bool vertTypeIsSkinningEnabled(u32 vertType) {
|
||||
if (g_Config.bSoftwareSkinning)
|
||||
return false;
|
||||
else
|
||||
return ((vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE);
|
||||
}
|
||||
|
||||
struct GPUStateCache_v0 {
|
||||
u32 vertexAddr;
|
||||
u32 indexAddr;
|
||||
|
||||
@@ -441,6 +441,11 @@ struct GPUgstate {
|
||||
void Restore(u32_le *ptr);
|
||||
};
|
||||
|
||||
bool vertTypeIsSkinningEnabled(u32 vertType);
|
||||
|
||||
inline int vertTypeGetNumBoneWeights(u32 vertType) { return 1 + ((vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT); }
|
||||
inline int vertTypeGetWeightMask(u32 vertType) { return vertType & GE_VTYPE_WEIGHT_MASK; }
|
||||
|
||||
// The rest is cached simplified/converted data for fast access.
|
||||
// Does not need to be saved when saving/restoring context.
|
||||
//
|
||||
@@ -553,6 +558,7 @@ struct GPUStateCache {
|
||||
bool allowShaderBlend;
|
||||
|
||||
float morphWeights[8];
|
||||
u32 deferredVertTypeDirty;
|
||||
|
||||
u32 curTextureWidth;
|
||||
u32 curTextureHeight;
|
||||
@@ -595,6 +601,9 @@ struct GPUStateCache {
|
||||
int spline_type_u;
|
||||
int spline_type_v;
|
||||
|
||||
bool useShaderDepal;
|
||||
GEBufferFormat depalFramebufferFormat;
|
||||
|
||||
u32 getRelativeAddress(u32 data) const;
|
||||
void Reset();
|
||||
void DoState(PointerWrap &p);
|
||||
|
||||
@@ -396,6 +396,10 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) {
|
||||
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
|
||||
}
|
||||
|
||||
if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) {
|
||||
DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType));
|
||||
}
|
||||
|
||||
GEPatchPrimType patchPrim = gstate.getPatchPrimitiveType();
|
||||
SetDrawType(DRAW_BEZIER, PatchPrimToPrim(patchPrim));
|
||||
|
||||
@@ -440,6 +444,10 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) {
|
||||
indices = Memory::GetPointerUnchecked(gstate_c.indexAddr);
|
||||
}
|
||||
|
||||
if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) {
|
||||
DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType));
|
||||
}
|
||||
|
||||
int sp_ucount = op & 0xFF;
|
||||
int sp_vcount = (op >> 8) & 0xFF;
|
||||
int sp_utype = (op >> 16) & 0x3;
|
||||
|
||||
@@ -167,6 +167,27 @@ VertexData TransformUnit::ReadVertex(VertexReader& vreader)
|
||||
vertex.normal = -vertex.normal;
|
||||
}
|
||||
|
||||
if (vertTypeIsSkinningEnabled(gstate.vertType) && !gstate.isModeThrough()) {
|
||||
float W[8] = { 1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f };
|
||||
vreader.ReadWeights(W);
|
||||
|
||||
Vec3<float> tmppos(0.f, 0.f, 0.f);
|
||||
Vec3<float> tmpnrm(0.f, 0.f, 0.f);
|
||||
|
||||
for (int i = 0; i < vertTypeGetNumBoneWeights(gstate.vertType); ++i) {
|
||||
Mat3x3<float> bone(&gstate.boneMatrix[12*i]);
|
||||
tmppos += (bone * ModelCoords(pos[0], pos[1], pos[2]) + Vec3<float>(gstate.boneMatrix[12*i+9], gstate.boneMatrix[12*i+10], gstate.boneMatrix[12*i+11])) * W[i];
|
||||
if (vreader.hasNormal())
|
||||
tmpnrm += (bone * vertex.normal) * W[i];
|
||||
}
|
||||
|
||||
pos[0] = tmppos.x;
|
||||
pos[1] = tmppos.y;
|
||||
pos[2] = tmppos.z;
|
||||
if (vreader.hasNormal())
|
||||
vertex.normal = tmpnrm;
|
||||
}
|
||||
|
||||
if (vreader.hasColor0()) {
|
||||
float col[4];
|
||||
vreader.ReadColor0(col);
|
||||
|
||||
@@ -64,9 +64,11 @@ enum { VAI_KILL_AGE = 120, VAI_UNRELIABLE_KILL_AGE = 240, VAI_UNRELIABLE_KILL_MA
|
||||
enum {
|
||||
DRAW_BINDING_TEXTURE = 0,
|
||||
DRAW_BINDING_2ND_TEXTURE = 1,
|
||||
DRAW_BINDING_DYNUBO_BASE = 2,
|
||||
DRAW_BINDING_DYNUBO_LIGHT = 3,
|
||||
DRAW_BINDING_TESS_STORAGE_BUF = 4,
|
||||
DRAW_BINDING_DEPAL_TEXTURE = 2,
|
||||
DRAW_BINDING_DYNUBO_BASE = 3,
|
||||
DRAW_BINDING_DYNUBO_LIGHT = 4,
|
||||
DRAW_BINDING_DYNUBO_BONE = 5,
|
||||
DRAW_BINDING_TESS_STORAGE_BUF = 6,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -94,7 +96,7 @@ DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan, Draw::DrawContext *dra
|
||||
|
||||
void DrawEngineVulkan::InitDeviceObjects() {
|
||||
// All resources we need for PSP drawing. Usually only bindings 0 and 2-4 are populated.
|
||||
VkDescriptorSetLayoutBinding bindings[5]{};
|
||||
VkDescriptorSetLayoutBinding bindings[7]{};
|
||||
bindings[0].descriptorCount = 1;
|
||||
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
@@ -104,18 +106,26 @@ void DrawEngineVulkan::InitDeviceObjects() {
|
||||
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[1].binding = DRAW_BINDING_2ND_TEXTURE;
|
||||
bindings[2].descriptorCount = 1;
|
||||
bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[2].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[2].binding = DRAW_BINDING_DYNUBO_BASE;
|
||||
bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; // sampler is ignored though.
|
||||
bindings[2].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[2].binding = DRAW_BINDING_DEPAL_TEXTURE;
|
||||
bindings[3].descriptorCount = 1;
|
||||
bindings[3].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[3].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
bindings[3].binding = DRAW_BINDING_DYNUBO_LIGHT;
|
||||
// Used only for hardware tessellation.
|
||||
bindings[3].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
bindings[3].binding = DRAW_BINDING_DYNUBO_BASE;
|
||||
bindings[4].descriptorCount = 1;
|
||||
bindings[4].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[4].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[4].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
bindings[4].binding = DRAW_BINDING_TESS_STORAGE_BUF;
|
||||
bindings[4].binding = DRAW_BINDING_DYNUBO_LIGHT;
|
||||
bindings[5].descriptorCount = 1;
|
||||
bindings[5].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
bindings[5].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
bindings[5].binding = DRAW_BINDING_DYNUBO_BONE;
|
||||
// Used only for hardware tessellation.
|
||||
bindings[6].descriptorCount = 1;
|
||||
bindings[6].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[6].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
bindings[6].binding = DRAW_BINDING_TESS_STORAGE_BUF;
|
||||
|
||||
VkDevice device = vulkan_->GetDevice();
|
||||
|
||||
@@ -129,7 +139,7 @@ void DrawEngineVulkan::InitDeviceObjects() {
|
||||
// if creating and updating them turns out to be expensive.
|
||||
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
|
||||
// We now create descriptor pools on demand, so removed from here.
|
||||
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 4 * 1024 * 1024);
|
||||
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 8 * 1024 * 1024);
|
||||
frame_[i].pushVertex = new VulkanPushBuffer(vulkan_, 2 * 1024 * 1024);
|
||||
frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 1 * 1024 * 1024);
|
||||
}
|
||||
@@ -360,9 +370,9 @@ VkResult DrawEngineVulkan::RecreateDescriptorPool(FrameData &frame, int newSize)
|
||||
frame.descPoolSize = newSize;
|
||||
|
||||
VkDescriptorPoolSize dpTypes[3];
|
||||
dpTypes[0].descriptorCount = frame.descPoolSize * 2;
|
||||
dpTypes[0].descriptorCount = frame.descPoolSize * 3;
|
||||
dpTypes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
dpTypes[1].descriptorCount = frame.descPoolSize * 2; // Don't use these for tess anymore, need max two per set.
|
||||
dpTypes[1].descriptorCount = frame.descPoolSize * 3; // Don't use these for tess anymore, need max three per set.
|
||||
dpTypes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
dpTypes[2].descriptorCount = frame.descPoolSize;
|
||||
dpTypes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
@@ -378,15 +388,18 @@ VkResult DrawEngineVulkan::RecreateDescriptorPool(FrameData &frame, int newSize)
|
||||
return res;
|
||||
}
|
||||
|
||||
VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, bool tess) {
|
||||
VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone, bool tess) {
|
||||
DescriptorSetKey key;
|
||||
key.imageView_ = imageView;
|
||||
key.sampler_ = sampler;
|
||||
key.secondaryImageView_ = boundSecondary_;
|
||||
key.depalImageView_ = boundDepal_;
|
||||
key.base_ = base;
|
||||
key.light_ = light;
|
||||
key.bone_ = bone;
|
||||
_dbg_assert_(G3D, base != VK_NULL_HANDLE);
|
||||
_dbg_assert_(G3D, light != VK_NULL_HANDLE);
|
||||
_dbg_assert_(G3D, bone != VK_NULL_HANDLE);
|
||||
|
||||
FrameData &frame = frame_[vulkan_->GetCurFrame()];
|
||||
// See if we already have this descriptor set cached.
|
||||
@@ -426,12 +439,11 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
// Even in release mode, this is bad.
|
||||
_assert_msg_(G3D, result == VK_SUCCESS, "Ran out of descriptor space in pool. sz=%d res=%d", (int)frame.descSets.size(), (int)result);
|
||||
|
||||
// We just don't write to the slots we don't care about.
|
||||
// We need 8 now that we support secondary texture bindings.
|
||||
VkWriteDescriptorSet writes[8]{};
|
||||
// We just don't write to the slots we don't care about, which is fine.
|
||||
VkWriteDescriptorSet writes[7]{};
|
||||
// Main texture
|
||||
int n = 0;
|
||||
VkDescriptorImageInfo tex[2]{};
|
||||
VkDescriptorImageInfo tex[3]{};
|
||||
if (imageView) {
|
||||
#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR
|
||||
tex[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
@@ -452,7 +464,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
|
||||
if (boundSecondary_) {
|
||||
#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR
|
||||
tex[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
tex[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
#else
|
||||
tex[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
#endif
|
||||
@@ -468,7 +480,23 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
n++;
|
||||
}
|
||||
|
||||
// Skipping 2nd texture for now.
|
||||
if (boundDepal_) {
|
||||
#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR
|
||||
tex[2].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
#else
|
||||
tex[2].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
#endif
|
||||
tex[2].imageView = boundDepal_;
|
||||
tex[2].sampler = samplerSecondary_; // doesn't matter, we use load
|
||||
writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[n].pNext = nullptr;
|
||||
writes[n].dstBinding = DRAW_BINDING_DEPAL_TEXTURE;
|
||||
writes[n].pImageInfo = &tex[2];
|
||||
writes[n].descriptorCount = 1;
|
||||
writes[n].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
writes[n].dstSet = desc;
|
||||
n++;
|
||||
}
|
||||
|
||||
// Tessellation data buffer. Make sure this is declared outside the if to avoid optimizer
|
||||
// shenanigans.
|
||||
@@ -494,7 +522,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
}
|
||||
|
||||
// Uniform buffer objects
|
||||
VkDescriptorBufferInfo buf[2]{};
|
||||
VkDescriptorBufferInfo buf[3]{};
|
||||
int count = 0;
|
||||
buf[count].buffer = base;
|
||||
buf[count].offset = 0;
|
||||
@@ -504,6 +532,10 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
buf[count].offset = 0;
|
||||
buf[count].range = sizeof(UB_VS_Lights);
|
||||
count++;
|
||||
buf[count].buffer = bone;
|
||||
buf[count].offset = 0;
|
||||
buf[count].range = sizeof(UB_VS_Bones);
|
||||
count++;
|
||||
for (int i = 0; i < count; i++) {
|
||||
writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[n].pNext = nullptr;
|
||||
@@ -527,9 +559,11 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView
|
||||
void DrawEngineVulkan::DirtyAllUBOs() {
|
||||
baseUBOOffset = 0;
|
||||
lightUBOOffset = 0;
|
||||
boneUBOOffset = 0;
|
||||
baseBuf = VK_NULL_HANDLE;
|
||||
lightBuf = VK_NULL_HANDLE;
|
||||
dirtyUniforms_ = DIRTY_BASE_UNIFORMS | DIRTY_LIGHT_UNIFORMS;
|
||||
boneBuf = VK_NULL_HANDLE;
|
||||
dirtyUniforms_ = DIRTY_BASE_UNIFORMS | DIRTY_LIGHT_UNIFORMS | DIRTY_BONE_UNIFORMS;
|
||||
imageView = VK_NULL_HANDLE;
|
||||
sampler = VK_NULL_HANDLE;
|
||||
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
|
||||
@@ -585,10 +619,10 @@ void DrawEngineVulkan::DoFlush() {
|
||||
|
||||
// Cannot cache vertex data with morph enabled.
|
||||
bool useCache = g_Config.bVertexCache && !(lastVType_ & GE_VTYPE_MORPHCOUNT_MASK);
|
||||
// Also avoid caching when skinning.
|
||||
// Also avoid caching when software skinning.
|
||||
VkBuffer vbuf = VK_NULL_HANDLE;
|
||||
VkBuffer ibuf = VK_NULL_HANDLE;
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
useCache = false;
|
||||
}
|
||||
|
||||
@@ -730,8 +764,8 @@ void DrawEngineVulkan::DoFlush() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
// If skinning, we've already predecoded into "decoded". So push that content.
|
||||
if (g_Config.bSoftwareSkinning && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
// If software skinning, we've already predecoded into "decoded". So push that content.
|
||||
VkDeviceSize size = decodedVerts_ * dec_->GetDecVtxFmt().stride;
|
||||
u8 *dest = (u8 *)frame->pushVertex->Push(size, &vbOffset, &vbuf);
|
||||
memcpy(dest, decoded, size);
|
||||
@@ -802,12 +836,12 @@ void DrawEngineVulkan::DoFlush() {
|
||||
dirtyUniforms_ |= shaderManager_->UpdateUniforms();
|
||||
UpdateUBOs(frame);
|
||||
|
||||
VkDescriptorSet ds = GetOrCreateDescriptorSet(imageView, sampler, baseBuf, lightBuf, tess);
|
||||
VkDescriptorSet ds = GetOrCreateDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf, tess);
|
||||
{
|
||||
PROFILE_THIS_SCOPE("renderman_q");
|
||||
|
||||
const uint32_t dynamicUBOOffsets[2] = {
|
||||
baseUBOOffset, lightUBOOffset,
|
||||
const uint32_t dynamicUBOOffsets[3] = {
|
||||
baseUBOOffset, lightUBOOffset, boneUBOOffset,
|
||||
};
|
||||
|
||||
int stride = dec_->GetDecVtxFmt().stride;
|
||||
@@ -908,9 +942,9 @@ void DrawEngineVulkan::DoFlush() {
|
||||
// Even if the first draw is through-mode, make sure we at least have one copy of these uniforms buffered
|
||||
UpdateUBOs(frame);
|
||||
|
||||
VkDescriptorSet ds = GetOrCreateDescriptorSet(imageView, sampler, baseBuf, lightBuf, tess);
|
||||
const uint32_t dynamicUBOOffsets[2] = {
|
||||
baseUBOOffset, lightUBOOffset,
|
||||
VkDescriptorSet ds = GetOrCreateDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf, tess);
|
||||
const uint32_t dynamicUBOOffsets[3] = {
|
||||
baseUBOOffset, lightUBOOffset, boneUBOOffset,
|
||||
};
|
||||
|
||||
PROFILE_THIS_SCOPE("renderman_q");
|
||||
@@ -977,6 +1011,10 @@ void DrawEngineVulkan::UpdateUBOs(FrameData *frame) {
|
||||
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
|
||||
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
|
||||
}
|
||||
}
|
||||
|
||||
DrawEngineVulkan::TessellationDataTransferVulkan::TessellationDataTransferVulkan(VulkanContext *vulkan, Draw::DrawContext *draw)
|
||||
@@ -986,14 +1024,11 @@ DrawEngineVulkan::TessellationDataTransferVulkan::TessellationDataTransferVulkan
|
||||
DrawEngineVulkan::TessellationDataTransferVulkan::~TessellationDataTransferVulkan() {
|
||||
}
|
||||
|
||||
// TODO: Consolidate the three textures into one, with height 3.
|
||||
// This can be done for all the backends.
|
||||
// TODO: Actually, even better, avoid the usage of textures altogether and just use shader storage buffers from the current pushbuffer.
|
||||
void DrawEngineVulkan::TessellationDataTransferVulkan::PrepareBuffers(float *&pos, float *&tex, float *&col, int &posStride, int &texStride, int &colStride, int size, bool hasColor, bool hasTexCoords) {
|
||||
colStride = 4;
|
||||
|
||||
// TODO: This SHOULD work without padding but I can't get it to work on nvidia, so had
|
||||
// to expand to vec4. Driver bug?
|
||||
// SSBOs that are not simply float1 or float2 need to be padded up to a float4 size. vec3 members
|
||||
// also need to be 16-byte aligned, hence the padding.
|
||||
struct TessData {
|
||||
float pos[3]; float pad1;
|
||||
float uv[2]; float pad2[2];
|
||||
@@ -1013,5 +1048,5 @@ void DrawEngineVulkan::TessellationDataTransferVulkan::PrepareBuffers(float *&po
|
||||
}
|
||||
|
||||
void DrawEngineVulkan::TessellationDataTransferVulkan::SendDataToShader(const float *pos, const float *tex, const float *col, int size, bool hasColor, bool hasTexCoords) {
|
||||
// Nothing to do here!
|
||||
// Nothing to do here! The caller will write directly to the pushbuffer through the pointers it got through PrepareBuffers.
|
||||
}
|
||||
@@ -20,10 +20,12 @@
|
||||
// The Descriptor Set used for the majority of PSP rendering looks like this:
|
||||
//
|
||||
// * binding 0: Texture/Sampler (the PSP texture)
|
||||
// * binding 1: Secondary texture sampler for shader blending or depal palettes
|
||||
// * binding 2: Base Uniform Buffer (includes fragment state)
|
||||
// * binding 3: Light uniform buffer
|
||||
// * binding 4: Shader buffer storage for tesselation
|
||||
// * binding 1: Secondary texture sampler for shader blending
|
||||
// * binding 2: Depal palette
|
||||
// * binding 3: Base Uniform Buffer (includes fragment state)
|
||||
// * binding 4: Light uniform buffer
|
||||
// * binding 5: Bone uniform buffer
|
||||
// * binding 6: Tess data storage buffer
|
||||
//
|
||||
// All shaders conform to this layout, so they are all compatible with the same descriptor set.
|
||||
// The format of the various uniform buffers may vary though - vertex shaders that don't skin
|
||||
@@ -177,6 +179,9 @@ public:
|
||||
}
|
||||
|
||||
void SetLineWidth(float lineWidth);
|
||||
void SetDepalTexture(VkImageView depal) {
|
||||
boundDepal_ = depal;
|
||||
}
|
||||
|
||||
private:
|
||||
struct FrameData;
|
||||
@@ -194,7 +199,7 @@ private:
|
||||
void DoFlush();
|
||||
void UpdateUBOs(FrameData *frame);
|
||||
|
||||
VkDescriptorSet GetOrCreateDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, bool tess);
|
||||
VkDescriptorSet GetOrCreateDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone, bool tess);
|
||||
|
||||
VulkanContext *vulkan_;
|
||||
Draw::DrawContext *draw_;
|
||||
@@ -207,6 +212,7 @@ private:
|
||||
|
||||
// Secondary texture for shader blending
|
||||
VkImageView boundSecondary_ = VK_NULL_HANDLE;
|
||||
VkImageView boundDepal_ = VK_NULL_HANDLE;
|
||||
VkSampler samplerSecondary_ = VK_NULL_HANDLE; // This one is actually never used since we use fetch.
|
||||
|
||||
PrehashMap<VertexArrayInfoVulkan *, nullptr> vai_;
|
||||
@@ -217,8 +223,9 @@ private:
|
||||
struct DescriptorSetKey {
|
||||
VkImageView imageView_;
|
||||
VkImageView secondaryImageView_;
|
||||
VkImageView depalImageView_;
|
||||
VkSampler sampler_;
|
||||
VkBuffer base_, light_; // All three UBO slots will be set to this. This will usually be identical
|
||||
VkBuffer base_, light_, bone_; // All three UBO slots will be set to this. This will usually be identical
|
||||
// for all draws in a frame, except when the buffer has to grow.
|
||||
};
|
||||
|
||||
@@ -252,7 +259,8 @@ private:
|
||||
uint64_t dirtyUniforms_;
|
||||
uint32_t baseUBOOffset;
|
||||
uint32_t lightUBOOffset;
|
||||
VkBuffer baseBuf, lightBuf;
|
||||
uint32_t boneUBOOffset;
|
||||
VkBuffer baseBuf, lightBuf, boneBuf;
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) {
|
||||
bool doTextureProjection = id.Bit(FS_BIT_DO_TEXTURE_PROJ);
|
||||
bool doTextureAlpha = id.Bit(FS_BIT_TEXALPHA);
|
||||
bool doFlatShading = id.Bit(FS_BIT_FLATSHADE);
|
||||
bool shaderDepal = id.Bit(FS_BIT_SHADER_DEPAL);
|
||||
|
||||
GEComparison alphaTestFunc = (GEComparison)id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3);
|
||||
GEComparison colorTestFunc = (GEComparison)id.Bits(FS_BIT_COLOR_TEST_FUNC, 2);
|
||||
@@ -81,7 +82,7 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) {
|
||||
|
||||
const char *shading = doFlatShading ? "flat" : "";
|
||||
|
||||
WRITE(p, "layout (std140, set = 0, binding = 2) uniform baseUBO {\n%s} base;\n", ub_baseStr);
|
||||
WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseUBO {\n%s} base;\n", ub_baseStr);
|
||||
if (doTexture) {
|
||||
WRITE(p, "layout (binding = 0) uniform sampler2D tex;\n");
|
||||
}
|
||||
@@ -92,6 +93,10 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
if (shaderDepal) {
|
||||
WRITE(p, "layout (binding = 2) uniform sampler2D pal;\n");
|
||||
}
|
||||
|
||||
WRITE(p, "layout (location = 1) %s in vec4 v_color0;\n", shading);
|
||||
if (lmode)
|
||||
WRITE(p, "layout (location = 2) %s in vec3 v_color1;\n", shading);
|
||||
@@ -173,10 +178,95 @@ bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer) {
|
||||
|
||||
if (doTextureProjection) {
|
||||
WRITE(p, " vec4 t = textureProj(tex, %s);\n", texcoord);
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " vec4 t1 = textureProjOffset(tex, %s, ivec2(1, 0));\n", texcoord);
|
||||
WRITE(p, " vec4 t2 = textureProjOffset(tex, %s, ivec2(0, 1));\n", texcoord);
|
||||
WRITE(p, " vec4 t3 = textureProjOffset(tex, %s, ivec2(1, 1));\n", texcoord);
|
||||
}
|
||||
} else {
|
||||
WRITE(p, " vec4 t = texture(tex, %s.xy);\n", texcoord);
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " vec4 t1 = textureOffset(tex, %s.xy, ivec2(1, 0));\n", texcoord);
|
||||
WRITE(p, " vec4 t2 = textureOffset(tex, %s.xy, ivec2(0, 1));\n", texcoord);
|
||||
WRITE(p, " vec4 t3 = textureOffset(tex, %s.xy, ivec2(1, 1));\n", texcoord);
|
||||
}
|
||||
}
|
||||
WRITE(p, " vec4 p = v_color0;\n");
|
||||
|
||||
if (shaderDepal) {
|
||||
WRITE(p, " uint depalMask = (base.depal_mask_shift_off_fmt & 0xFF);\n");
|
||||
WRITE(p, " uint depalShift = (base.depal_mask_shift_off_fmt >> 8) & 0xFF;\n");
|
||||
WRITE(p, " uint depalOffset = ((base.depal_mask_shift_off_fmt >> 16) & 0xFF) << 4;\n");
|
||||
WRITE(p, " uint depalFmt = (base.depal_mask_shift_off_fmt >> 24) & 0x3;\n");
|
||||
WRITE(p, " bool bilinear = (base.depal_mask_shift_off_fmt >> 31) != 0;\n");
|
||||
WRITE(p, " vec2 fraction = fract(%s.xy * vec2(textureSize(tex, 0).xy));\n", texcoord);
|
||||
WRITE(p, " uvec4 col; uint index0; uint index1; uint index2; uint index3;\n");
|
||||
WRITE(p, " switch (depalFmt) {\n"); // We might want to include fmt in the shader ID if this is a performance issue.
|
||||
WRITE(p, " case 0:\n"); // 565
|
||||
WRITE(p, " col = uvec4(t.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index0 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = uvec4(t1.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index1 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t2.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index2 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t3.rgb * vec3(31.99, 63.99, 31.99), 0);\n");
|
||||
WRITE(p, " index3 = (col.b << 11) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 1:\n"); // 5551
|
||||
WRITE(p, " col = uvec4(t.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index0 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = uvec4(t1.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index1 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t2.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index2 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t3.rgba * vec4(31.99, 31.99, 31.99, 1.0));\n");
|
||||
WRITE(p, " index3 = (col.a << 15) | (col.b << 10) | (col.g << 5) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 2:\n"); // 4444
|
||||
WRITE(p, " col = uvec4(t.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index0 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = uvec4(t1.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index1 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t2.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index2 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t3.rgba * vec4(15.99, 15.99, 15.99, 15.99));\n");
|
||||
WRITE(p, " index3 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " case 3:\n"); // 8888
|
||||
WRITE(p, " col = uvec4(t.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index0 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " col = uvec4(t1.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index1 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t2.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index2 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " col = uvec4(t3.rgba * vec4(255.99, 255.99, 255.99, 255.99));\n");
|
||||
WRITE(p, " index3 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " break;\n");
|
||||
WRITE(p, " };\n");
|
||||
WRITE(p, " index0 = ((index0 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " t = texelFetch(pal, ivec2(index0, 0), 0);\n");
|
||||
WRITE(p, " if (bilinear) {\n");
|
||||
WRITE(p, " index1 = ((index1 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " index2 = ((index2 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " index3 = ((index3 >> depalShift) & depalMask) | depalOffset;\n");
|
||||
WRITE(p, " t1 = texelFetch(pal, ivec2(index1, 0), 0);\n");
|
||||
WRITE(p, " t2 = texelFetch(pal, ivec2(index2, 0), 0);\n");
|
||||
WRITE(p, " t3 = texelFetch(pal, ivec2(index3, 0), 0);\n");
|
||||
WRITE(p, " t = mix(t, t1, fraction.x);\n");
|
||||
WRITE(p, " t2 = mix(t2, t3, fraction.x);\n");
|
||||
WRITE(p, " t = mix(t, t2, fraction.y);\n");
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
|
||||
if (texFunc != GE_TEXFUNC_REPLACE || !doTextureAlpha)
|
||||
WRITE(p, " vec4 p = v_color0;\n");
|
||||
|
||||
if (doTextureAlpha) { // texfmt == RGBA
|
||||
switch (texFunc) {
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
#include "GPU/Vulkan/FramebufferVulkan.h"
|
||||
#include "GPU/Vulkan/DrawEngineVulkan.h"
|
||||
#include "GPU/Vulkan/TextureCacheVulkan.h"
|
||||
#include "thin3d/VulkanRenderManager.h"
|
||||
#include "thin3d/VulkanQueueRunner.h"
|
||||
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/HLE/sceKernelThread.h"
|
||||
@@ -252,6 +254,7 @@ void GPU_Vulkan::CheckGPUFeatures() {
|
||||
|
||||
void GPU_Vulkan::BeginHostFrame() {
|
||||
drawEngine_.BeginFrame();
|
||||
UpdateCmdInfo();
|
||||
|
||||
if (resized_) {
|
||||
CheckGPUFeatures();
|
||||
@@ -455,6 +458,16 @@ void GPU_Vulkan::InitDeviceObjects() {
|
||||
assert(!frameData_[i].push_);
|
||||
frameData_[i].push_ = new VulkanPushBuffer(vulkan_, 64 * 1024);
|
||||
}
|
||||
|
||||
VulkanRenderManager *rm = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
||||
uint32_t hacks = 0;
|
||||
if (PSP_CoreParameter().compat.flags().MGS2AcidHack)
|
||||
hacks |= QUEUE_HACK_MGS2_ACID;
|
||||
if (PSP_CoreParameter().compat.flags().SonicRivalsHack)
|
||||
hacks |= QUEUE_HACK_SONIC;
|
||||
if (hacks) {
|
||||
rm->GetQueueRunner()->EnableHacks(hacks);
|
||||
}
|
||||
}
|
||||
|
||||
void GPU_Vulkan::DestroyDeviceObjects() {
|
||||
@@ -466,6 +479,10 @@ void GPU_Vulkan::DestroyDeviceObjects() {
|
||||
frameData_[i].push_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to turn off hacks when shutting down the GPU. Don't want them running in the menu.
|
||||
VulkanRenderManager *rm = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
||||
rm->GetQueueRunner()->EnableHacks(0);
|
||||
}
|
||||
|
||||
void GPU_Vulkan::DeviceLost() {
|
||||
@@ -490,6 +507,7 @@ void GPU_Vulkan::DeviceRestore() {
|
||||
|
||||
CheckGPUFeatures();
|
||||
BuildReportingInfo();
|
||||
UpdateCmdInfo();
|
||||
|
||||
framebufferManagerVulkan_->DeviceRestore(vulkan_, draw_);
|
||||
vulkan2D_.DeviceRestore(vulkan_);
|
||||
|
||||
@@ -76,6 +76,8 @@ static const DeclTypeInfo VComp[] = {
|
||||
};
|
||||
|
||||
static void VertexAttribSetup(VkVertexInputAttributeDescription *attr, int fmt, int offset, PspAttributeLocation location) {
|
||||
assert(fmt != DEC_NONE);
|
||||
assert(fmt < ARRAY_SIZE(VComp));
|
||||
attr->location = (uint32_t)location;
|
||||
attr->binding = 0;
|
||||
attr->format = VComp[fmt].type;
|
||||
@@ -87,6 +89,12 @@ static void VertexAttribSetup(VkVertexInputAttributeDescription *attr, int fmt,
|
||||
// as we will only call this code when we need to create a new VkPipeline.
|
||||
static int SetupVertexAttribs(VkVertexInputAttributeDescription attrs[], const DecVtxFormat &decFmt) {
|
||||
int count = 0;
|
||||
if (decFmt.w0fmt != 0) {
|
||||
VertexAttribSetup(&attrs[count++], decFmt.w0fmt, decFmt.w0off, PspAttributeLocation::W1);
|
||||
}
|
||||
if (decFmt.w1fmt != 0) {
|
||||
VertexAttribSetup(&attrs[count++], decFmt.w1fmt, decFmt.w1off, PspAttributeLocation::W2);
|
||||
}
|
||||
if (decFmt.uvfmt != 0) {
|
||||
VertexAttribSetup(&attrs[count++], decFmt.uvfmt, decFmt.uvoff, PspAttributeLocation::TEXCOORD);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id,
|
||||
std::string errorMessage;
|
||||
std::vector<uint32_t> spirv;
|
||||
#ifdef SHADERLOG
|
||||
OutputDebugStringA(code);
|
||||
OutputDebugStringA(LineNumberString(code).c_str());
|
||||
#endif
|
||||
|
||||
bool success = GLSLtoSPV(VK_SHADER_STAGE_FRAGMENT_BIT, code, spirv, &errorMessage);
|
||||
@@ -62,9 +62,9 @@ VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id,
|
||||
ERROR_LOG(G3D, "Messages: %s", errorMessage.c_str());
|
||||
ERROR_LOG(G3D, "Shader source:\n%s", code);
|
||||
#ifdef SHADERLOG
|
||||
OutputDebugStringA(LineNumberString(code).c_str());
|
||||
OutputDebugStringA("Messages:\n");
|
||||
OutputDebugStringA(errorMessage.c_str());
|
||||
OutputDebugStringA(LineNumberString(code).c_str());
|
||||
#endif
|
||||
Reporting::ReportMessage("Vulkan error in shader compilation: info: %s / code: %s", errorMessage.c_str(), code);
|
||||
} else {
|
||||
@@ -106,7 +106,7 @@ VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, cons
|
||||
std::string errorMessage;
|
||||
std::vector<uint32_t> spirv;
|
||||
#ifdef SHADERLOG
|
||||
OutputDebugStringA(code);
|
||||
OutputDebugStringA(LineNumberString(code).c_str());
|
||||
#endif
|
||||
bool success = GLSLtoSPV(VK_SHADER_STAGE_VERTEX_BIT, code, spirv, &errorMessage);
|
||||
if (!errorMessage.empty()) {
|
||||
@@ -117,8 +117,11 @@ VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, cons
|
||||
}
|
||||
ERROR_LOG(G3D, "Messages: %s", errorMessage.c_str());
|
||||
ERROR_LOG(G3D, "Shader source:\n%s", code);
|
||||
#ifdef SHADERLOG
|
||||
OutputDebugStringA(LineNumberString(code).c_str());
|
||||
OutputDebugStringUTF8("Messages:\n");
|
||||
OutputDebugStringUTF8(errorMessage.c_str());
|
||||
#endif
|
||||
Reporting::ReportMessage("Vulkan error in shader compilation: info: %s / code: %s", errorMessage.c_str(), code);
|
||||
} else {
|
||||
success = vulkan_->CreateShaderModule(spirv, &module_);
|
||||
@@ -159,9 +162,11 @@ ShaderManagerVulkan::ShaderManagerVulkan(VulkanContext *vulkan)
|
||||
uboAlignment_ = vulkan_->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment;
|
||||
memset(&ub_base, 0, sizeof(ub_base));
|
||||
memset(&ub_lights, 0, sizeof(ub_lights));
|
||||
memset(&ub_bones, 0, sizeof(ub_bones));
|
||||
|
||||
ILOG("sizeof(ub_base): %d", (int)sizeof(ub_base));
|
||||
ILOG("sizeof(ub_lights): %d", (int)sizeof(ub_lights));
|
||||
ILOG("sizeof(ub_bones): %d", (int)sizeof(ub_bones));
|
||||
}
|
||||
|
||||
ShaderManagerVulkan::~ShaderManagerVulkan() {
|
||||
@@ -214,6 +219,8 @@ uint64_t ShaderManagerVulkan::UpdateUniforms() {
|
||||
BaseUpdateUniforms(&ub_base, dirty, false);
|
||||
if (dirty & DIRTY_LIGHT_UNIFORMS)
|
||||
LightUpdateUniforms(&ub_lights, dirty);
|
||||
if (dirty & DIRTY_BONE_UNIFORMS)
|
||||
BoneUpdateUniforms(&ub_bones, dirty);
|
||||
}
|
||||
gstate_c.CleanUniforms();
|
||||
return dirty;
|
||||
@@ -351,7 +358,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM
|
||||
// instantaneous.
|
||||
|
||||
#define CACHE_HEADER_MAGIC 0xff51f420
|
||||
#define CACHE_VERSION 9
|
||||
#define CACHE_VERSION 13
|
||||
struct VulkanCacheHeader {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
|
||||
@@ -111,6 +111,7 @@ public:
|
||||
// Applies dirty changes and copies the buffer.
|
||||
bool IsBaseDirty() { return true; }
|
||||
bool IsLightDirty() { return true; }
|
||||
bool IsBoneDirty() { return true; }
|
||||
|
||||
uint32_t PushBaseBuffer(VulkanPushBuffer *dest, VkBuffer *buf) {
|
||||
return dest->PushAligned(&ub_base, sizeof(ub_base), uboAlignment_, buf);
|
||||
@@ -118,6 +119,10 @@ public:
|
||||
uint32_t PushLightBuffer(VulkanPushBuffer *dest, VkBuffer *buf) {
|
||||
return dest->PushAligned(&ub_lights, sizeof(ub_lights), uboAlignment_, buf);
|
||||
}
|
||||
// TODO: Only push half the bone buffer if we only have four bones.
|
||||
uint32_t PushBoneBuffer(VulkanPushBuffer *dest, VkBuffer *buf) {
|
||||
return dest->PushAligned(&ub_bones, sizeof(ub_bones), uboAlignment_, buf);
|
||||
}
|
||||
|
||||
bool LoadCache(FILE *f);
|
||||
void SaveCache(FILE *f);
|
||||
@@ -139,6 +144,7 @@ private:
|
||||
// Uniform block scratchpad. These (the relevant ones) are copied to the current pushbuffer at draw time.
|
||||
UB_VS_FS_Base ub_base;
|
||||
UB_VS_Lights ub_lights;
|
||||
UB_VS_Bones ub_bones;
|
||||
|
||||
VulkanFragmentShader *lastFShader_;
|
||||
VulkanVertexShader *lastVShader_;
|
||||
|
||||
@@ -327,6 +327,8 @@ void TextureCacheVulkan::BindTexture(TexCacheEntry *entry) {
|
||||
SamplerCacheKey key{};
|
||||
UpdateSamplingParams(*entry, key);
|
||||
curSampler_ = samplerCache_.GetOrCreateSampler(key);
|
||||
drawEngine_->SetDepalTexture(VK_NULL_HANDLE);
|
||||
gstate_c.useShaderDepal = false;
|
||||
}
|
||||
|
||||
void TextureCacheVulkan::Unbind() {
|
||||
@@ -336,10 +338,39 @@ void TextureCacheVulkan::Unbind() {
|
||||
}
|
||||
|
||||
void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFramebuffer *framebuffer) {
|
||||
SamplerCacheKey samplerKey{};
|
||||
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, samplerKey);
|
||||
|
||||
DepalShaderVulkan *depalShader = nullptr;
|
||||
uint32_t clutMode = gstate.clutformat & 0xFFFFFF;
|
||||
|
||||
bool useShaderDepal = true;
|
||||
|
||||
if ((entry->status & TexCacheEntry::STATUS_DEPALETTIZE) && !g_Config.bDisableSlowFramebufEffects) {
|
||||
depalShader = depalShaderCache_->GetDepalettizeShader(clutMode, framebuffer->drawnFormat);
|
||||
if (useShaderDepal) {
|
||||
depalShaderCache_->SetPushBuffer(drawEngine_->GetPushBufferForTextureData());
|
||||
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
|
||||
VulkanTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_);
|
||||
drawEngine_->SetDepalTexture(clutTexture->GetImageView());
|
||||
// Only point filtering enabled.
|
||||
samplerKey.magFilt = false;
|
||||
samplerKey.minFilt = false;
|
||||
samplerKey.mipFilt = false;
|
||||
// Make sure to update the uniforms.
|
||||
gstate_c.Dirty(DIRTY_DEPAL);
|
||||
gstate_c.useShaderDepal = true;
|
||||
gstate_c.depalFramebufferFormat = framebuffer->drawnFormat;
|
||||
const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16);
|
||||
const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor;
|
||||
TexCacheEntry::TexStatus alphaStatus = CheckAlpha(clutBuf_, getClutDestFormatVulkan(clutFormat), clutTotalColors, clutTotalColors, 1);
|
||||
gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL);
|
||||
curSampler_ = samplerCache_.GetOrCreateSampler(samplerKey);
|
||||
InvalidateLastTexture(entry);
|
||||
imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET);
|
||||
return;
|
||||
} else {
|
||||
depalShader = depalShaderCache_->GetDepalettizeShader(clutMode, framebuffer->drawnFormat);
|
||||
}
|
||||
}
|
||||
if (depalShader) {
|
||||
depalShaderCache_->SetPushBuffer(drawEngine_->GetPushBufferForTextureData());
|
||||
@@ -425,18 +456,16 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr
|
||||
|
||||
// Need to rebind the pipeline since we switched it.
|
||||
drawEngine_->DirtyPipeline();
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
// Since we may have switched render targets, we need to re-set depth/stencil etc states.
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_BLEND_STATE | DIRTY_RASTER_STATE);
|
||||
} else {
|
||||
entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE;
|
||||
|
||||
framebufferManager_->RebindFramebuffer();
|
||||
framebufferManager_->RebindFramebuffer(); // TODO: This line should usually not be needed.
|
||||
imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET);
|
||||
|
||||
gstate_c.SetTextureFullAlpha(gstate.getTextureFormat() == GE_TFMT_5650);
|
||||
}
|
||||
|
||||
SamplerCacheKey samplerKey{};
|
||||
SetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight, samplerKey);
|
||||
curSampler_ = samplerCache_.GetOrCreateSampler(samplerKey);
|
||||
InvalidateLastTexture(entry);
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ public:
|
||||
lastBoundTexture = nullptr;
|
||||
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
|
||||
}
|
||||
|
||||
void InvalidateLastTexture(TexCacheEntry *entry = nullptr) override {
|
||||
if (!entry || entry->vkTex == lastBoundTexture) {
|
||||
lastBoundTexture = nullptr;
|
||||
|
||||
@@ -51,6 +51,18 @@ static const char *vulkan_glsl_preamble =
|
||||
#undef WRITE
|
||||
#define WRITE p+=sprintf
|
||||
|
||||
static const char * const boneWeightDecl[9] = {
|
||||
"#ERROR#",
|
||||
"layout(location = 3) in float w1;\n",
|
||||
"layout(location = 3) in vec2 w1;\n",
|
||||
"layout(location = 3) in vec3 w1;\n",
|
||||
"layout(location = 3) in vec4 w1;\n",
|
||||
"layout(location = 3) in vec4 w1;\nlayout(location = 4) in float w2;\n",
|
||||
"layout(location = 3) in vec4 w1;\nlayout(location = 4) in vec2 w2;\n",
|
||||
"layout(location = 3) in vec4 w1;\nlayout(location = 4) in vec3 w2;\n",
|
||||
"layout(location = 3) in vec4 w1;\nlayout(location = 4) in vec4 w2;\n",
|
||||
};
|
||||
|
||||
enum DoLightComputation {
|
||||
LIGHT_OFF,
|
||||
LIGHT_SHADE,
|
||||
@@ -118,6 +130,7 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) {
|
||||
bool flipNormal = id.Bit(VS_BIT_NORM_REVERSE);
|
||||
int ls0 = id.Bits(VS_BIT_LS0, 2);
|
||||
int ls1 = id.Bits(VS_BIT_LS1, 2);
|
||||
bool enableBones = id.Bit(VS_BIT_ENABLE_BONES);
|
||||
bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE);
|
||||
int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3);
|
||||
|
||||
@@ -128,9 +141,11 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) {
|
||||
bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS);
|
||||
|
||||
WRITE(p, "\n");
|
||||
WRITE(p, "layout (std140, set = 0, binding = 2) uniform baseVars {\n%s} base;\n", ub_baseStr);
|
||||
WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseVars {\n%s} base;\n", ub_baseStr);
|
||||
if (enableLighting || doShadeMapping)
|
||||
WRITE(p, "layout (std140, set = 0, binding = 3) uniform lightVars {\n%s} light;\n", ub_vs_lightsStr);
|
||||
WRITE(p, "layout (std140, set = 0, binding = 4) uniform lightVars {\n%s} light;\n", ub_vs_lightsStr);
|
||||
if (enableBones)
|
||||
WRITE(p, "layout (std140, set = 0, binding = 5) uniform boneVars {\n%s} bone;\n", ub_vs_bonesStr);
|
||||
|
||||
const char *shading = doFlatShading ? "flat " : "";
|
||||
|
||||
@@ -146,6 +161,13 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
int numBoneWeights = 0;
|
||||
int boneWeightScale = id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2);
|
||||
if (enableBones) {
|
||||
numBoneWeights = 1 + id.Bits(VS_BIT_BONES, 3);
|
||||
WRITE(p, "%s", boneWeightDecl[numBoneWeights]);
|
||||
}
|
||||
|
||||
if (useHWTransform)
|
||||
WRITE(p, "layout (location = %d) in vec3 position;\n", (int)PspAttributeLocation::POSITION);
|
||||
else
|
||||
@@ -198,14 +220,14 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) {
|
||||
WRITE(p, "out gl_PerVertex { vec4 gl_Position; };\n");
|
||||
|
||||
if (doBezier || doSpline) {
|
||||
WRITE(p, "layout (std430) struct TessData {\n");
|
||||
WRITE(p, "struct TessData {\n");
|
||||
WRITE(p, " vec4 pos;\n");
|
||||
WRITE(p, " vec4 uv;\n");
|
||||
WRITE(p, " vec4 color;\n");
|
||||
WRITE(p, "};");
|
||||
WRITE(p, "layout (std430, set = 0, binding = 4) buffer s_tess_data {\n");
|
||||
WRITE(p, "layout (std430, set = 0, binding = 6) buffer s_tess_data {\n");
|
||||
WRITE(p, " TessData data[];");
|
||||
WRITE(p, "} tess_data;");
|
||||
WRITE(p, "} tess_data;\n");
|
||||
|
||||
for (int i = 2; i <= 4; i++) {
|
||||
// Define 3 types vec2, vec3, vec4
|
||||
@@ -314,109 +336,144 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) {
|
||||
WRITE(p, " gl_Position = outPos;\n");
|
||||
} else {
|
||||
// Step 1: World Transform / Skinning
|
||||
if (doBezier || doSpline) {
|
||||
WRITE(p, " vec3 _pos[16];\n");
|
||||
WRITE(p, " vec2 _tex[16];\n");
|
||||
WRITE(p, " vec4 _col[16];\n");
|
||||
WRITE(p, " int num_patches_u = %s;\n", doBezier ? "(base.spline_count_u - 1) / 3" : "base.spline_count_u - 3");
|
||||
WRITE(p, " int u = int(mod(gl_InstanceIndex, num_patches_u));\n");
|
||||
WRITE(p, " int v = gl_InstanceIndex / num_patches_u;\n");
|
||||
WRITE(p, " ivec2 patch_pos = ivec2(u, v);\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " for (int j = 0; j < 4; j++) {\n");
|
||||
WRITE(p, " int idx = (i + v%s) * base.spline_count_u + (j + u%s);\n", doBezier ? " * 3" : "", doBezier ? " * 3" : "");
|
||||
WRITE(p, " _pos[i * 4 + j] = tess_data.data[idx].pos.xyz;\n");
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[i * 4 + j] = tess_data.data[idx].uv.xy;\n");
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[i * 4 + j] = tess_data.data[idx].color;\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " vec2 tess_pos = position.xy;\n");
|
||||
WRITE(p, " vec2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1 - tess_pos) * (1 - tess_pos) * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3 * tess_pos * (1 - tess_pos) * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3 * tess_pos * tess_pos * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " ivec2 spline_num_patches = ivec2(base.spline_count_u - 3, base.spline_count_v - 3);\n");
|
||||
WRITE(p, " ivec2 spline_type = ivec2(base.spline_type_u, base.spline_type_v);\n");
|
||||
WRITE(p, " vec2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + patch_pos, knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " vec3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " vec2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec2 tex = tess_pos + patch_pos;\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " vec4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec4 col = tess_data.data[0].color;\n");
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
if (!enableBones) {
|
||||
if (doBezier || doSpline) {
|
||||
WRITE(p, " vec3 _pos[16];\n");
|
||||
WRITE(p, " vec2 _tex[16];\n");
|
||||
WRITE(p, " vec4 _col[16];\n");
|
||||
WRITE(p, " int spline_count_u = int(base.spline_counts & 0xff);\n");
|
||||
WRITE(p, " int spline_count_v = int((base.spline_counts >> 8) & 0xff);\n");
|
||||
WRITE(p, " int num_patches_u = %s;\n", doBezier ? "(spline_count_u - 1) / 3" : "spline_count_u - 3");
|
||||
WRITE(p, " int u = int(mod(gl_InstanceIndex, num_patches_u));\n");
|
||||
WRITE(p, " int v = gl_InstanceIndex / num_patches_u;\n");
|
||||
WRITE(p, " ivec2 patch_pos = ivec2(u, v);\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " for (int j = 0; j < 4; j++) {\n");
|
||||
WRITE(p, " int idx = (i + v%s) * spline_count_u + (j + u%s);\n", doBezier ? " * 3" : "", doBezier ? " * 3" : "");
|
||||
WRITE(p, " _pos[i * 4 + j] = tess_data.data[idx].pos.xyz;\n");
|
||||
if (doTexture && hasTexcoord && hasTexcoordTess)
|
||||
WRITE(p, " _tex[i * 4 + j] = tess_data.data[idx].uv.xy;\n");
|
||||
if (hasColor && hasColorTess)
|
||||
WRITE(p, " _col[i * 4 + j] = tess_data.data[idx].color;\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " vec2 tess_pos = position.xy;\n");
|
||||
WRITE(p, " vec2 weights[4];\n");
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " vec2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3 * (tess_pos - 1) * (tess_pos - 1); \n");
|
||||
WRITE(p, " bernderiv[1] = 9 * tess_pos * tess_pos - 12 * tess_pos + 3; \n");
|
||||
WRITE(p, " bernderiv[2] = 3 * (2 - 3 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " vec2 bernderiv_u[4];\n");
|
||||
WRITE(p, " vec2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = vec2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = vec2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " vec3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " vec3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
// Bernstein 3D
|
||||
WRITE(p, " weights[0] = (1 - tess_pos) * (1 - tess_pos) * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[1] = 3 * tess_pos * (1 - tess_pos) * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[2] = 3 * tess_pos * tess_pos * (1 - tess_pos);\n");
|
||||
WRITE(p, " weights[3] = tess_pos * tess_pos * tess_pos;\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " vec2 tess_next_u = vec2(normal.x, 0);\n");
|
||||
WRITE(p, " vec2 tess_next_v = vec2(0, normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " vec2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " vec2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " vec2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " vec2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_u = tess_sample(_pos, weights);\n");
|
||||
WRITE(p, " ivec2 spline_num_patches = ivec2(spline_count_u - 3, spline_count_v - 3);\n");
|
||||
WRITE(p, " int spline_type_u = int((base.spline_counts >> 16) & 0xff);\n");
|
||||
WRITE(p, " int spline_type_v = int((base.spline_counts >> 24) & 0xff);\n");
|
||||
WRITE(p, " ivec2 spline_type = ivec2(spline_type_u, spline_type_v);\n");
|
||||
WRITE(p, " vec2 knots[6];\n");
|
||||
WRITE(p, " spline_knot(spline_num_patches, spline_type, knots, patch_pos);\n");
|
||||
WRITE(p, " spline_weight(tess_pos + patch_pos, knots, weights);\n");
|
||||
}
|
||||
WRITE(p, " vec3 pos = tess_sample(_pos, weights);\n");
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (hasTexcoordTess)
|
||||
WRITE(p, " vec2 tex = tess_sample(_tex, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec2 tex = tess_pos + patch_pos;\n");
|
||||
}
|
||||
if (hasColor) {
|
||||
if (hasColorTess)
|
||||
WRITE(p, " vec4 col = tess_sample(_col, weights);\n");
|
||||
else
|
||||
WRITE(p, " vec4 col = tess_data.data[0].color;\n");
|
||||
}
|
||||
if (hasNormal) {
|
||||
// Curved surface is probably always need to compute normal(not sampling from control points)
|
||||
if (doBezier) {
|
||||
// Bernstein derivative
|
||||
WRITE(p, " vec2 bernderiv[4];\n");
|
||||
WRITE(p, " bernderiv[0] = -3 * (tess_pos - 1) * (tess_pos - 1); \n");
|
||||
WRITE(p, " bernderiv[1] = 9 * tess_pos * tess_pos - 12 * tess_pos + 3; \n");
|
||||
WRITE(p, " bernderiv[2] = 3 * (2 - 3 * tess_pos) * tess_pos; \n");
|
||||
WRITE(p, " bernderiv[3] = 3 * tess_pos * tess_pos; \n");
|
||||
|
||||
WRITE(p, " vec3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " vec3 dv = pos_d - pos_u;\n");
|
||||
WRITE(p, " vec2 bernderiv_u[4];\n");
|
||||
WRITE(p, " vec2 bernderiv_v[4];\n");
|
||||
WRITE(p, " for (int i = 0; i < 4; i++) {\n");
|
||||
WRITE(p, " bernderiv_u[i] = vec2(bernderiv[i].x, weights[i].y);\n");
|
||||
WRITE(p, " bernderiv_v[i] = vec2(weights[i].x, bernderiv[i].y);\n");
|
||||
WRITE(p, " }\n");
|
||||
|
||||
WRITE(p, " vec3 du = tess_sample(_pos, bernderiv_u);\n");
|
||||
WRITE(p, " vec3 dv = tess_sample(_pos, bernderiv_v);\n");
|
||||
} else { // Spline
|
||||
WRITE(p, " vec2 tess_next_u = vec2(normal.x, 0);\n");
|
||||
WRITE(p, " vec2 tess_next_v = vec2(0, normal.y);\n");
|
||||
// Right
|
||||
WRITE(p, " vec2 tess_pos_r = tess_pos + tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_r + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_r = tess_sample(_pos, weights);\n");
|
||||
// Left
|
||||
WRITE(p, " vec2 tess_pos_l = tess_pos - tess_next_u;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_l + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_l = tess_sample(_pos, weights);\n");
|
||||
// Down
|
||||
WRITE(p, " vec2 tess_pos_d = tess_pos + tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_d + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_d = tess_sample(_pos, weights);\n");
|
||||
// Up
|
||||
WRITE(p, " vec2 tess_pos_u = tess_pos - tess_next_v;\n");
|
||||
WRITE(p, " spline_weight(tess_pos_u + patch_pos, knots, weights);\n");
|
||||
WRITE(p, " vec3 pos_u = tess_sample(_pos, weights);\n");
|
||||
|
||||
WRITE(p, " vec3 du = pos_r - pos_l;\n");
|
||||
WRITE(p, " vec3 dv = pos_d - pos_u;\n");
|
||||
}
|
||||
WRITE(p, " vec3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
}
|
||||
WRITE(p, " vec3 worldpos = vec4(pos.xyz, 1.0) * base.world_mtx;\n");
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize(vec4(%snrm, 0.0) * base.world_mtx);\n", flipNormalTess ? "-" : "");
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
}
|
||||
WRITE(p, " vec3 nrm = cross(du, dv);\n");
|
||||
WRITE(p, " nrm = normalize(nrm);\n");
|
||||
}
|
||||
WRITE(p, " vec3 worldpos = vec4(pos.xyz, 1.0) * base.world_mtx;\n");
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize(vec4(%snrm, 0.0) * base.world_mtx);\n", flipNormalTess ? "-" : "");
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
// No skinning, just standard T&L.
|
||||
WRITE(p, " vec3 worldpos = vec4(position.xyz, 1.0) * base.world_mtx;\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize(vec4(%snormal, 0.0) * base.world_mtx);\n", flipNormal ? "-" : "");
|
||||
else
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else {
|
||||
WRITE(p, " vec3 worldpos = vec4(position.xyz, 1.0) * base.world_mtx;\n");
|
||||
if (hasNormal)
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize(vec4(%snormal, 0.0) * base.world_mtx);\n", flipNormal ? "-" : "");
|
||||
else
|
||||
WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n");
|
||||
static const char *rescale[4] = { "", " * 1.9921875", " * 1.999969482421875", "" }; // 2*127.5f/128.f, 2*32767.5f/32768.f, 1.0f};
|
||||
const char *factor = rescale[boneWeightScale];
|
||||
|
||||
static const char * const boneWeightAttr[8] = {
|
||||
"w1.x", "w1.y", "w1.z", "w1.w",
|
||||
"w2.x", "w2.y", "w2.z", "w2.w",
|
||||
};
|
||||
|
||||
WRITE(p, " mat3x4 skinMatrix = w1.x * bone.m[0];\n");
|
||||
if (numBoneWeights > 1) {
|
||||
for (int i = 1; i < numBoneWeights; i++) {
|
||||
WRITE(p, " skinMatrix += %s * bone.m[%i];\n", boneWeightAttr[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
WRITE(p, ";\n");
|
||||
|
||||
// Trying to simplify this results in bugs in LBP...
|
||||
WRITE(p, " vec3 skinnedpos = (vec4(position, 1.0) * skinMatrix) %s;\n", factor);
|
||||
WRITE(p, " vec3 worldpos = vec4(skinnedpos, 1.0) * base.world_mtx;\n");
|
||||
|
||||
if (hasNormal) {
|
||||
WRITE(p, " mediump vec3 skinnednormal = vec4(%snormal, 0.0) * skinMatrix %s;\n", flipNormal ? "-" : "", factor);
|
||||
} else {
|
||||
WRITE(p, " mediump vec3 skinnednormal = vec4(0.0, 0.0, %s1.0, 0.0) * skinMatrix %s;\n", flipNormal ? "-" : "", factor);
|
||||
}
|
||||
WRITE(p, " mediump vec3 worldnormal = normalize(vec4(skinnednormal, 0.0) * base.world_mtx);\n");
|
||||
}
|
||||
|
||||
WRITE(p, " vec4 viewPos = vec4(vec4(worldpos, 1.0) * base.view_mtx, 1.0);\n");
|
||||
|
||||
@@ -157,6 +157,9 @@ bool EmuScreen::bootAllowStorage(const std::string &filename) {
|
||||
case PERMISSION_STATUS_GRANTED:
|
||||
return true;
|
||||
}
|
||||
|
||||
_assert_(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void EmuScreen::bootGame(const std::string &filename) {
|
||||
|
||||
@@ -318,6 +318,14 @@ void GameSettingsScreen::CreateViews() {
|
||||
hwTransform->OnClick.Handle(this, &GameSettingsScreen::OnHardwareTransform);
|
||||
hwTransform->SetDisabledPtr(&g_Config.bSoftwareRendering);
|
||||
|
||||
|
||||
CheckBox *swSkin = graphicsSettings->Add(new CheckBox(&g_Config.bSoftwareSkinning, gr->T("Software Skinning")));
|
||||
swSkin->OnClick.Add([=](EventParams &e) {
|
||||
settingInfo_->Show(gr->T("SoftwareSkinning Tip", "Combine skinned model draws on the CPU, faster in most games"), e.v);
|
||||
return UI::EVENT_CONTINUE;
|
||||
});
|
||||
swSkin->SetDisabledPtr(&g_Config.bSoftwareRendering);
|
||||
|
||||
CheckBox *vtxCache = graphicsSettings->Add(new CheckBox(&g_Config.bVertexCache, gr->T("Vertex Cache")));
|
||||
vtxCache->OnClick.Add([=](EventParams &e) {
|
||||
settingInfo_->Show(gr->T("VertexCache Tip", "Faster, but may cause temporary flicker"), e.v);
|
||||
|
||||
@@ -551,7 +551,6 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,
|
||||
if (renderer_inited) {
|
||||
// Would be really nice if we could get something on the GL thread immediately when shutting down...
|
||||
ILOG("NativeApp.displayInit() restoring");
|
||||
graphicsContext->ThreadEnd();
|
||||
if (useCPUThread) {
|
||||
EmuThreadStop();
|
||||
while (graphicsContext->ThreadFrame()) {
|
||||
@@ -561,6 +560,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,
|
||||
} else {
|
||||
NativeShutdownGraphics();
|
||||
}
|
||||
graphicsContext->ThreadEnd();
|
||||
graphicsContext->ShutdownFromRenderThread();
|
||||
|
||||
ILOG("Shut down both threads. Now let's bring it up again!");
|
||||
|
||||
+15
-1
@@ -444,4 +444,18 @@ NPHG00092 = true
|
||||
NPEG00044 = true
|
||||
NPJG00120 = true
|
||||
UCJS10114 = true
|
||||
UCES01401 = true
|
||||
UCES01401 = true
|
||||
|
||||
[MGS2AcidHack]
|
||||
ULES00008 = true
|
||||
ULJM08001 = true
|
||||
ULJM05001 = true
|
||||
ULAS42007 = true
|
||||
ULUS10006 = true
|
||||
ULUS10077 = true
|
||||
|
||||
[SonicRivalsHack]
|
||||
ULES00622 = true # SR1
|
||||
ULUS10195 = true # SR1
|
||||
ULUS10323 = true # SR2
|
||||
ULES00940 = true # SR2
|
||||
@@ -157,3 +157,7 @@ inline void ExpandFloat24x3ToFloat4(float dest[4], const uint32_t src[3]) {
|
||||
memcpy(dest, temp, sizeof(float) * 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint32_t BytesToUint32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
|
||||
return (a) | (b << 8) | (c << 16) | (d << 24);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "GLRenderManager.h"
|
||||
#include "DataFormatGL.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stringutil.h"
|
||||
#include "gfx/gl_common.h"
|
||||
#include "gfx/gl_debug_log.h"
|
||||
#include "gfx_es2/gpu_features.h"
|
||||
@@ -68,6 +69,7 @@ void GLQueueRunner::DestroyDeviceObjects() {
|
||||
}
|
||||
|
||||
void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
GLuint boundTexture = (GLuint)-1;
|
||||
bool allocatedTextures = false;
|
||||
@@ -102,6 +104,7 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
}
|
||||
case GLRInitStepType::CREATE_PROGRAM:
|
||||
{
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
GLRProgram *program = step.create_program.program;
|
||||
program->program = glCreateProgram();
|
||||
_assert_msg_(G3D, step.create_program.num_shaders > 0, "Can't create a program with zero shaders");
|
||||
@@ -154,14 +157,16 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
|
||||
#ifdef _WIN32
|
||||
OutputDebugStringUTF8(buf);
|
||||
OutputDebugStringUTF8(vsCode);
|
||||
if (vsCode)
|
||||
OutputDebugStringUTF8(LineNumberString(vsCode).c_str());
|
||||
if (fsCode)
|
||||
OutputDebugStringUTF8(fsCode);
|
||||
OutputDebugStringUTF8(LineNumberString(fsCode).c_str());
|
||||
#endif
|
||||
delete[] buf;
|
||||
} else {
|
||||
ELOG("Could not link program with %d shaders for unknown reason:", step.create_program.num_shaders);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -185,10 +190,12 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRInitStepType::CREATE_SHADER:
|
||||
{
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
GLuint shader = glCreateShader(step.create_shader.stage);
|
||||
step.create_shader.shader->shader = shader;
|
||||
const char *code = step.create_shader.code;
|
||||
@@ -221,6 +228,7 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
step.create_shader.shader->code = code;
|
||||
delete[] step.create_shader.code;
|
||||
step.create_shader.shader->valid = true;
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRInitStepType::CREATE_INPUT_LAYOUT:
|
||||
@@ -231,9 +239,11 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
}
|
||||
case GLRInitStepType::CREATE_FRAMEBUFFER:
|
||||
{
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
boundTexture = (GLuint)-1;
|
||||
InitCreateFramebuffer(step);
|
||||
allocatedTextures = true;
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRInitStepType::TEXTURE_IMAGE:
|
||||
@@ -263,27 +273,34 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps) {
|
||||
glTexParameteri(tex->target, GL_TEXTURE_WRAP_T, tex->wrapT);
|
||||
glTexParameteri(tex->target, GL_TEXTURE_MAG_FILTER, tex->magFilter);
|
||||
glTexParameteri(tex->target, GL_TEXTURE_MIN_FILTER, tex->minFilter);
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRInitStepType::TEXTURE_FINALIZE:
|
||||
{
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
GLRTexture *tex = step.texture_finalize.texture;
|
||||
if (boundTexture != tex->texture) {
|
||||
glBindTexture(tex->target, tex->texture);
|
||||
boundTexture = tex->texture;
|
||||
}
|
||||
glTexParameteri(tex->target, GL_TEXTURE_MAX_LEVEL, step.texture_finalize.maxLevel);
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
glTexParameteri(tex->target, GL_TEXTURE_MAX_LEVEL, step.texture_finalize.maxLevel);
|
||||
}
|
||||
tex->maxLod = (float)step.texture_finalize.maxLevel;
|
||||
if (step.texture_finalize.genMips) {
|
||||
glGenerateMipmap(tex->target);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
Crash();
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
|
||||
// TODO: Use GL_KHR_no_error or a debug callback, where supported?
|
||||
if (allocatedTextures) {
|
||||
@@ -333,7 +350,9 @@ void GLQueueRunner::InitCreateFramebuffer(const GLRInitStep &step) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, fbo->color_texture.wrapT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, fbo->color_texture.magFilter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, fbo->color_texture.minFilter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
if (!gl_extensions.IsGLES || gl_extensions.GLES3) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
}
|
||||
|
||||
if (gl_extensions.IsGLES) {
|
||||
if (gl_extensions.OES_packed_depth_stencil) {
|
||||
@@ -752,6 +771,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
glBindTexture(GL_TEXTURE_2D, 0); // Which target? Well we only use this one anyway...
|
||||
curTex[slot] = nullptr;
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::BIND_FB_TEXTURE:
|
||||
@@ -769,6 +789,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
// TODO: Depth texturing?
|
||||
curTex[slot] = nullptr;
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::BINDPROGRAM:
|
||||
@@ -804,6 +825,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
auto &entry = layout->entries[i];
|
||||
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.bindVertexBuffer.offset + entry.offset));
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::BIND_BUFFER:
|
||||
@@ -822,6 +844,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
assert(!c.bind_buffer.buffer->Mapped());
|
||||
glBindBuffer(c.bind_buffer.target, buf);
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::GENMIPS:
|
||||
@@ -841,6 +864,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
break;
|
||||
case GLRRenderCommand::TEXTURESAMPLER:
|
||||
{
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
GLint slot = c.textureSampler.slot;
|
||||
if (slot != activeSlot) {
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
@@ -874,6 +898,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
}
|
||||
tex->anisotropy = c.textureSampler.anisotropy;
|
||||
}
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
break;
|
||||
}
|
||||
case GLRRenderCommand::TEXTURELOD:
|
||||
@@ -957,6 +982,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step) {
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
#endif
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
}
|
||||
|
||||
void GLQueueRunner::PerformCopy(const GLRStep &step) {
|
||||
@@ -1016,6 +1042,7 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) {
|
||||
srcRect.w, srcRect.h, depth);
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR_IF_DEBUG();
|
||||
}
|
||||
|
||||
void GLQueueRunner::PerformReadback(const GLRStep &pass) {
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
#define VLOG(...)
|
||||
#endif
|
||||
|
||||
static std::thread::id renderThreadId;
|
||||
static bool OnRenderThread() {
|
||||
return std::this_thread::get_id() == renderThreadId;
|
||||
}
|
||||
|
||||
// Runs on the GPU thread.
|
||||
void GLDeleter::Perform(GLRenderManager *renderManager) {
|
||||
for (auto pushBuffer : pushBuffers) {
|
||||
@@ -65,6 +70,7 @@ GLRenderManager::~GLRenderManager() {
|
||||
void GLRenderManager::ThreadStart() {
|
||||
queueRunner_.CreateDeviceObjects();
|
||||
threadFrame_ = threadInitFrame_;
|
||||
renderThreadId = std::this_thread::get_id();
|
||||
|
||||
bool mapBuffers = (gl_extensions.bugs & BUG_ANY_MAP_BUFFER_RANGE_SLOW) == 0;
|
||||
bool hasBufferStorage = gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage;
|
||||
@@ -466,6 +472,7 @@ void GLRenderManager::Run(int frame) {
|
||||
auto &initStepsOnThread = frameData_[frame].initSteps;
|
||||
// queueRunner_.LogSteps(stepsOnThread);
|
||||
queueRunner_.RunInitSteps(initStepsOnThread);
|
||||
initStepsOnThread.clear();
|
||||
|
||||
// Run this after RunInitSteps so any fresh GLRBuffers for the pushbuffers can get created.
|
||||
for (auto iter : frameData.activePushBuffers) {
|
||||
@@ -475,7 +482,6 @@ void GLRenderManager::Run(int frame) {
|
||||
|
||||
queueRunner_.RunSteps(stepsOnThread);
|
||||
stepsOnThread.clear();
|
||||
initStepsOnThread.clear();
|
||||
|
||||
for (auto iter : frameData.activePushBuffers) {
|
||||
iter->MapDevice(bufferStrategy_);
|
||||
@@ -574,7 +580,7 @@ GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size)
|
||||
}
|
||||
|
||||
GLPushBuffer::~GLPushBuffer() {
|
||||
Destroy();
|
||||
Destroy(true);
|
||||
}
|
||||
|
||||
void GLPushBuffer::Map() {
|
||||
@@ -606,6 +612,8 @@ void GLPushBuffer::Unmap() {
|
||||
|
||||
void GLPushBuffer::Flush() {
|
||||
// Must be called from the render thread.
|
||||
_dbg_assert_(G3D, OnRenderThread());
|
||||
|
||||
buffers_[buf_].flushOffset = offset_;
|
||||
if (!buffers_[buf_].deviceMemory && writePtr_) {
|
||||
auto &info = buffers_[buf_];
|
||||
@@ -647,12 +655,18 @@ bool GLPushBuffer::AddBuffer() {
|
||||
}
|
||||
|
||||
// Executed on the render thread!
|
||||
void GLPushBuffer::Destroy() {
|
||||
void GLPushBuffer::Destroy(bool onRenderThread) {
|
||||
for (BufInfo &info : buffers_) {
|
||||
// This will automatically unmap device memory, if needed.
|
||||
// NOTE: We immediately delete the buffer, don't go through the deleter, since we're on the render thread.
|
||||
// render_->DeleteBuffer(info.buffer);
|
||||
delete info.buffer;
|
||||
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
|
||||
if (onRenderThread) {
|
||||
_dbg_assert_(G3D, OnRenderThread());
|
||||
delete info.buffer;
|
||||
} else {
|
||||
_dbg_assert_(G3D, !OnRenderThread());
|
||||
render_->DeleteBuffer(info.buffer);
|
||||
}
|
||||
|
||||
FreeAlignedMemory(info.localMemory);
|
||||
}
|
||||
buffers_.clear();
|
||||
@@ -684,6 +698,8 @@ void GLPushBuffer::NextBuffer(size_t minSize) {
|
||||
}
|
||||
|
||||
void GLPushBuffer::Defragment() {
|
||||
_dbg_assert_(G3D, std::this_thread::get_id() != renderThreadId);
|
||||
|
||||
if (buffers_.size() <= 1) {
|
||||
// Let's take this chance to jetison localMemory we don't need.
|
||||
for (auto &info : buffers_) {
|
||||
@@ -698,7 +714,7 @@ void GLPushBuffer::Defragment() {
|
||||
|
||||
// Okay, we have more than one. Destroy them all and start over with a larger one.
|
||||
size_t newSize = size_ * buffers_.size();
|
||||
Destroy();
|
||||
Destroy(false);
|
||||
|
||||
size_ = newSize;
|
||||
bool res = AddBuffer();
|
||||
@@ -714,6 +730,8 @@ size_t GLPushBuffer::GetTotalSize() const {
|
||||
}
|
||||
|
||||
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
|
||||
_dbg_assert_(G3D, OnRenderThread());
|
||||
|
||||
strategy_ = strategy;
|
||||
if (strategy_ == GLBufferStrategy::SUBDATA) {
|
||||
return;
|
||||
@@ -746,6 +764,8 @@ void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
|
||||
}
|
||||
|
||||
void GLPushBuffer::UnmapDevice() {
|
||||
_dbg_assert_(G3D, OnRenderThread());
|
||||
|
||||
for (auto &info : buffers_) {
|
||||
if (info.deviceMemory) {
|
||||
// TODO: Technically this can return false?
|
||||
|
||||
@@ -288,7 +288,7 @@ private:
|
||||
bool AddBuffer();
|
||||
void NextBuffer(size_t minSize);
|
||||
void Defragment();
|
||||
void Destroy();
|
||||
void Destroy(bool onRenderThread);
|
||||
|
||||
GLRenderManager *render_;
|
||||
std::vector<BufInfo> buffers_;
|
||||
|
||||
@@ -347,7 +347,7 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) {
|
||||
return pass;
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vector<VKRStep *> &steps) {
|
||||
void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector<VKRStep *> &steps) {
|
||||
// Optimizes renderpasses, then sequences them.
|
||||
// Planned optimizations:
|
||||
// * Create copies of render target that are rendered to multiple times and textured from in sequence, and push those render passes
|
||||
@@ -397,6 +397,17 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vector<VKRStep
|
||||
}
|
||||
}
|
||||
|
||||
// Queue hacks.
|
||||
if (hacksEnabled_) {
|
||||
if (hacksEnabled_ & QUEUE_HACK_MGS2_ACID) {
|
||||
// Massive speedup.
|
||||
ApplyMGSHack(steps);
|
||||
}
|
||||
if (hacksEnabled_ & QUEUE_HACK_SONIC) {
|
||||
ApplySonicHack(steps);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < steps.size(); i++) {
|
||||
const VKRStep &step = *steps[i];
|
||||
switch (step.stepType) {
|
||||
@@ -422,6 +433,146 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vector<VKRStep
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::ApplyMGSHack(std::vector<VKRStep *> &steps) {
|
||||
// We want to turn a sequence of copy,render(1),copy,render(1),copy,render(1) to copy,copy,copy,render(n).
|
||||
|
||||
for (int i = 0; i < (int)steps.size() - 3; i++) {
|
||||
int last = -1;
|
||||
if (!(steps[i]->stepType == VKRStepType::COPY &&
|
||||
steps[i + 1]->stepType == VKRStepType::RENDER &&
|
||||
steps[i + 2]->stepType == VKRStepType::COPY &&
|
||||
steps[i + 1]->render.numDraws == 1 &&
|
||||
steps[i]->copy.dst == steps[i + 2]->copy.dst))
|
||||
continue;
|
||||
// Looks promising! Let's start by finding the last one.
|
||||
for (int j = i; j < (int)steps.size(); j++) {
|
||||
switch (steps[j]->stepType) {
|
||||
case VKRStepType::RENDER:
|
||||
if (steps[j]->render.numDraws > 1)
|
||||
last = j - 1;
|
||||
break;
|
||||
case VKRStepType::COPY:
|
||||
if (steps[j]->copy.dst != steps[i]->copy.dst)
|
||||
last = j - 1;
|
||||
break;
|
||||
}
|
||||
if (last != -1)
|
||||
break;
|
||||
}
|
||||
|
||||
if (last != -1) {
|
||||
// We've got a sequence from i to last that needs reordering.
|
||||
// First, let's sort it, keeping the same length.
|
||||
std::vector<VKRStep *> copies;
|
||||
std::vector<VKRStep *> renders;
|
||||
copies.reserve((last - i) / 2);
|
||||
renders.reserve((last - i) / 2);
|
||||
for (int n = i; n <= last; n++) {
|
||||
if (steps[n]->stepType == VKRStepType::COPY)
|
||||
copies.push_back(steps[n]);
|
||||
else if (steps[n]->stepType == VKRStepType::RENDER)
|
||||
renders.push_back(steps[n]);
|
||||
}
|
||||
// Write the copies back. TODO: Combine them too.
|
||||
for (int j = 0; j < (int)copies.size(); j++) {
|
||||
steps[i + j] = copies[j];
|
||||
}
|
||||
// Write the renders back (so they will be deleted properly).
|
||||
for (int j = 0; j < (int)renders.size(); j++) {
|
||||
steps[i + j + copies.size()] = renders[j];
|
||||
}
|
||||
assert(steps[i + copies.size()]->stepType == VKRStepType::RENDER);
|
||||
// Combine the renders.
|
||||
for (int j = 1; j < (int)renders.size(); j++) {
|
||||
for (int k = 0; k < renders[j]->commands.size(); k++) {
|
||||
steps[i + copies.size()]->commands.push_back(renders[j]->commands[k]);
|
||||
}
|
||||
steps[i + copies.size() + j]->stepType = VKRStepType::RENDER_SKIP;
|
||||
}
|
||||
// We're done.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::ApplySonicHack(std::vector<VKRStep *> &steps) {
|
||||
// We want to turn a sequence of render(3),render(1),render(6),render(1),render(6),render(1),render(3) to
|
||||
// render(1), render(1), render(1), render(6), render(6), render(6)
|
||||
|
||||
for (int i = 0; i < (int)steps.size() - 4; i++) {
|
||||
int last = -1;
|
||||
if (!(steps[i]->stepType == VKRStepType::RENDER &&
|
||||
steps[i + 1]->stepType == VKRStepType::RENDER &&
|
||||
steps[i + 2]->stepType == VKRStepType::RENDER &&
|
||||
steps[i + 3]->stepType == VKRStepType::RENDER &&
|
||||
steps[i]->render.numDraws == 3 &&
|
||||
steps[i + 1]->render.numDraws == 1 &&
|
||||
steps[i + 2]->render.numDraws == 6 &&
|
||||
steps[i + 3]->render.numDraws == 1 &&
|
||||
steps[i]->render.framebuffer == steps[i + 2]->render.framebuffer &&
|
||||
steps[i + 1]->render.framebuffer == steps[i + 3]->render.framebuffer))
|
||||
continue;
|
||||
// Looks promising! Let's start by finding the last one.
|
||||
for (int j = i; j < (int)steps.size(); j++) {
|
||||
switch (steps[j]->stepType) {
|
||||
case VKRStepType::RENDER:
|
||||
if ((j - i) & 1) {
|
||||
if (steps[j]->render.framebuffer != steps[i + 1]->render.framebuffer)
|
||||
last = j - 1;
|
||||
if (steps[j]->render.numDraws != 1)
|
||||
last = j - 1;
|
||||
} else {
|
||||
if (steps[j]->render.framebuffer != steps[i]->render.framebuffer)
|
||||
last = j - 1;
|
||||
if (steps[j]->render.numDraws != 3 && steps[j]->render.numDraws != 6)
|
||||
last = j - 1;
|
||||
}
|
||||
}
|
||||
if (last != -1)
|
||||
break;
|
||||
}
|
||||
|
||||
if (last != -1) {
|
||||
// We've got a sequence from i to last that needs reordering.
|
||||
// First, let's sort it, keeping the same length.
|
||||
std::vector<VKRStep *> type1;
|
||||
std::vector<VKRStep *> type2;
|
||||
type1.reserve((last - i) / 2);
|
||||
type2.reserve((last - i) / 2);
|
||||
for (int n = i; n <= last; n++) {
|
||||
if (steps[n]->render.framebuffer == steps[i]->render.framebuffer)
|
||||
type1.push_back(steps[n]);
|
||||
else
|
||||
type2.push_back(steps[n]);
|
||||
}
|
||||
|
||||
// Write the renders back in order. Same amount, so deletion will work fine.
|
||||
for (int j = 0; j < (int)type1.size(); j++) {
|
||||
steps[i + j] = type1[j];
|
||||
}
|
||||
for (int j = 0; j < (int)type2.size(); j++) {
|
||||
steps[i + j + type1.size()] = type2[j];
|
||||
}
|
||||
|
||||
// Combine the renders.
|
||||
for (int j = 1; j < (int)type1.size(); j++) {
|
||||
for (int k = 0; k < (int)type1[j]->commands.size(); k++) {
|
||||
steps[i]->commands.push_back(type1[j]->commands[k]);
|
||||
}
|
||||
steps[i + j]->stepType = VKRStepType::RENDER_SKIP;
|
||||
}
|
||||
for (int j = 1; j < (int)type2.size(); j++) {
|
||||
for (int k = 0; k < (int)type2[j]->commands.size(); k++) {
|
||||
steps[i + type1.size()]->commands.push_back(type2[j]->commands[k]);
|
||||
}
|
||||
steps[i + j + type1.size()]->stepType = VKRStepType::RENDER_SKIP;
|
||||
}
|
||||
// We're done.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanQueueRunner::LogSteps(const std::vector<VKRStep *> &steps) {
|
||||
ILOG("=======================================");
|
||||
for (size_t i = 0; i < steps.size(); i++) {
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
class VKRFramebuffer;
|
||||
struct VKRImage;
|
||||
|
||||
enum {
|
||||
QUEUE_HACK_MGS2_ACID = 1,
|
||||
QUEUE_HACK_SONIC = 2,
|
||||
};
|
||||
|
||||
enum class VKRRenderCommand : uint8_t {
|
||||
BIND_PIPELINE,
|
||||
STENCIL,
|
||||
@@ -41,7 +46,7 @@ struct VkRenderData {
|
||||
VkPipelineLayout pipelineLayout;
|
||||
VkDescriptorSet ds;
|
||||
int numUboOffsets;
|
||||
uint32_t uboOffsets[2];
|
||||
uint32_t uboOffsets[3];
|
||||
VkBuffer vbuffer; // might need to increase at some point
|
||||
VkDeviceSize voffset;
|
||||
VkBuffer ibuffer;
|
||||
@@ -152,7 +157,9 @@ public:
|
||||
backbuffer_ = fb;
|
||||
backbufferImage_ = img;
|
||||
}
|
||||
void RunSteps(VkCommandBuffer cmd, const std::vector<VKRStep *> &steps);
|
||||
|
||||
// RunSteps can modify steps but will leave it in a valid state.
|
||||
void RunSteps(VkCommandBuffer cmd, std::vector<VKRStep *> &steps);
|
||||
void LogSteps(const std::vector<VKRStep *> &steps);
|
||||
|
||||
void CreateDeviceObjects();
|
||||
@@ -205,6 +212,10 @@ public:
|
||||
return found;
|
||||
}
|
||||
|
||||
void EnableHacks(uint32_t hacks) {
|
||||
hacksEnabled_ = hacks;
|
||||
}
|
||||
|
||||
private:
|
||||
void InitBackbufferRenderPass();
|
||||
|
||||
@@ -223,6 +234,9 @@ private:
|
||||
|
||||
void ResizeReadbackBuffer(VkDeviceSize requiredSize);
|
||||
|
||||
void ApplyMGSHack(std::vector<VKRStep *> &steps);
|
||||
void ApplySonicHack(std::vector<VKRStep *> &steps);
|
||||
|
||||
static void SetupTransitionToTransferSrc(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect);
|
||||
static void SetupTransitionToTransferDst(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect);
|
||||
|
||||
@@ -244,4 +258,7 @@ private:
|
||||
VkDeviceMemory readbackMemory_ = VK_NULL_HANDLE;
|
||||
VkBuffer readbackBuffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceSize readbackBufferSize_ = 0;
|
||||
|
||||
// TODO: Enable based on compat.ini.
|
||||
uint32_t hacksEnabled_ = 0;
|
||||
};
|
||||
@@ -369,6 +369,7 @@ int main(int argc, const char* argv[])
|
||||
g_Config.bFrameSkipUnthrottle = false;
|
||||
g_Config.bEnableLogging = fullLog;
|
||||
g_Config.iNumWorkerThreads = 1;
|
||||
g_Config.bSoftwareSkinning = true;
|
||||
g_Config.iATRACMP3Volume = MAX_CONFIG_VOLUME;
|
||||
g_Config.iSASVolume = MAX_CONFIG_VOLUME;
|
||||
g_Config.bVertexDecoderJit = true;
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// SubtleVolume.h
|
||||
// subtleVolumeObjC
|
||||
//
|
||||
// Created by iMokhles on 24/03/16.
|
||||
// Copyright © 2016 iMokhles. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
/**
|
||||
The style of the volume indicator
|
||||
- Plain: A plain bar
|
||||
- RoundedLine: A plain bar with rounded corners
|
||||
- Dashes: A bar divided in dashes
|
||||
- Dots: A bar composed by a line of dots
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, SubtleVolumeStyle) {
|
||||
SubtleVolumeStylePlain,
|
||||
SubtleVolumeStyleRoundedLine,
|
||||
SubtleVolumeStyleDashes,
|
||||
SubtleVolumeStyleDots
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
The entry and exit animation of the volume indicator
|
||||
- None: The indicator is always visible
|
||||
- SlideDown: The indicator fades in/out and slides from/to the top into position
|
||||
- FadeIn: The indicator fades in and out
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, SubtleVolumeAnimation) {
|
||||
SubtleVolumeAnimationNone,
|
||||
SubtleVolumeAnimationSlideDown,
|
||||
SubtleVolumeAnimationFadeIn
|
||||
};
|
||||
|
||||
@class SubtleVolume;
|
||||
/**
|
||||
Delegate protocol fo `SubtleVolume`.
|
||||
Notifies the delegate when a change is about to happen (before the entry animation)
|
||||
and when a change occurred (and the exit animation is complete)
|
||||
*/
|
||||
@protocol SubtleVolumeDelegate <NSObject>
|
||||
/**
|
||||
The volume is about to change. This is fired before performing any entry animation
|
||||
- parameter subtleVolume: The current instance of `SubtleVolume`
|
||||
- parameter value: The value of the volume (between 0 an 1.0)
|
||||
*/
|
||||
- (void)subtleVolume:(SubtleVolume *)volumeView willChange:(CGFloat)value;
|
||||
/**
|
||||
The volume did change. This is fired after the exit animation is done
|
||||
- parameter subtleVolume: The current instance of `SubtleVolume`
|
||||
- parameter value: The value of the volume (between 0 an 1.0)
|
||||
*/
|
||||
- (void)subtleVolume:(SubtleVolume *)volumeView didChange:(CGFloat)value;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
Replace the system volume popup with a more subtle way to display the volume
|
||||
when the user changes it with the volume rocker.
|
||||
*/
|
||||
@interface SubtleVolume : UIView
|
||||
/**
|
||||
The style of the volume indicator
|
||||
*/
|
||||
@property (nonatomic, assign) SubtleVolumeStyle style;
|
||||
/**
|
||||
The entry and exit animation of the indicator. The animation is triggered by the volume
|
||||
If the animation is set to `SubtleVolumeAnimationNone`, the volume indicator is always visible
|
||||
*/
|
||||
@property (nonatomic, assign) SubtleVolumeAnimation animation;
|
||||
@property (nonatomic, strong) UIColor *barBackgroundColor;
|
||||
@property (nonatomic, strong) UIColor *barTintColor;
|
||||
@property (nonatomic, assign) id <SubtleVolumeDelegate> delegate;
|
||||
@property (nonatomic, assign) BOOL animatedByDefault;
|
||||
@property (nonatomic, assign) CGFloat padding;
|
||||
|
||||
- (instancetype)initWithStyle:(SubtleVolumeStyle)style;
|
||||
- (instancetype)initWithStyle:(SubtleVolumeStyle)style frame:(CGRect)frame;
|
||||
@end
|
||||
@@ -0,0 +1,238 @@
|
||||
//
|
||||
// SubtleVolume.m
|
||||
// subtleVolumeObjC
|
||||
//
|
||||
// Created by iMokhles on 24/03/16.
|
||||
// Copyright © 2016 iMokhles. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SubtleVolume.h"
|
||||
|
||||
MPVolumeView *volume = [[MPVolumeView alloc] initWithFrame:CGRectZero];
|
||||
UIView *overlay = [[UIView alloc] init];
|
||||
CGFloat volumeLevel = 0;
|
||||
|
||||
@interface SubtleVolume (){
|
||||
BOOL runningShowAnimation;
|
||||
BOOL showing;
|
||||
BOOL runningHideAnimation;
|
||||
BOOL lastAnimated;
|
||||
};
|
||||
|
||||
@property (nonatomic, strong) NSTimer *timer;
|
||||
|
||||
- (void)timerComplete;
|
||||
- (void)doHide:(BOOL)animated;
|
||||
- (void)doShow:(BOOL)animated;
|
||||
- (void)stopAnimations;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation SubtleVolume
|
||||
|
||||
- (instancetype)initWithStyle:(SubtleVolumeStyle)style frame:(CGRect)frame {
|
||||
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
self.animatedByDefault = YES;
|
||||
self.style = style;
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStyle:(SubtleVolumeStyle)style {
|
||||
return [self initWithStyle:style frame:CGRectZero];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = nil;
|
||||
NSAssert(false, @"To init this class please use the designated initializer: initWithStyle or initWithStyle:frame:");
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setup {
|
||||
@try {
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:nil];
|
||||
} @catch (NSException *e) {
|
||||
NSLog(@"Unable to initialize AVAudioSession");
|
||||
}
|
||||
|
||||
volumeLevel = [[AVAudioSession sharedInstance] outputVolume];
|
||||
[[AVAudioSession sharedInstance] addObserver:self forKeyPath:@"outputVolume" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
[volume setVolumeThumbImage:[[UIImage alloc] init] forState:UIControlStateNormal];
|
||||
[volume setUserInteractionEnabled:NO];
|
||||
[volume setAlpha:0.0001];
|
||||
[volume setShowsRouteButton:NO];
|
||||
self.alpha = 0.0001;
|
||||
|
||||
[self addSubview:volume];
|
||||
|
||||
[self addSubview:overlay];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
overlay.frame = CGRectMake(
|
||||
self.padding,
|
||||
self.padding,
|
||||
(self.frame.size.width - (self.padding*2)) * volumeLevel,
|
||||
self.frame.size.height - (self.padding*2)
|
||||
);
|
||||
|
||||
self.backgroundColor = self.barBackgroundColor;
|
||||
overlay.backgroundColor = self.barTintColor;
|
||||
|
||||
}
|
||||
- (void)updateVolume:(CGFloat)value animated:(BOOL)animated {
|
||||
[self.delegate subtleVolume:self willChange:value];
|
||||
volumeLevel = value;
|
||||
lastAnimated = animated;
|
||||
[UIView animateWithDuration:(animated ? 0.1 : 0) animations:^{
|
||||
CGRect rectOverlayView = overlay.frame;
|
||||
CGFloat overlyWidth = self.frame.size.width * volumeLevel;
|
||||
rectOverlayView.size.width = overlyWidth;
|
||||
overlay.frame = rectOverlayView;
|
||||
}];
|
||||
|
||||
|
||||
if(self.timer) {
|
||||
[self.timer invalidate];
|
||||
self.timer = nil;
|
||||
}
|
||||
|
||||
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerComplete) userInfo:nil repeats:NO];
|
||||
[self doShow:animated];
|
||||
[self.delegate subtleVolume:self didChange:value];
|
||||
}
|
||||
|
||||
- (void)timerComplete {
|
||||
[self doHide:lastAnimated];
|
||||
self.timer = nil;
|
||||
}
|
||||
|
||||
- (void)doHide:(BOOL)animated {
|
||||
if(!showing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(runningHideAnimation && !animated) {
|
||||
[self stopAnimations];
|
||||
}
|
||||
|
||||
if(runningHideAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(animated) {
|
||||
runningHideAnimation = YES;
|
||||
[UIView animateWithDuration:0.333 animations:^{
|
||||
switch (self.animation) {
|
||||
case SubtleVolumeAnimationNone:
|
||||
break;
|
||||
case SubtleVolumeAnimationFadeIn:
|
||||
self.alpha = 0.0001;
|
||||
break;
|
||||
case SubtleVolumeAnimationSlideDown:
|
||||
self.alpha = 0.0001;
|
||||
self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} completion:^(BOOL finished) {
|
||||
showing = NO;
|
||||
runningHideAnimation = NO;
|
||||
}];
|
||||
} else {
|
||||
showing = NO;
|
||||
self.alpha = 0.0001;
|
||||
if(self.animation == SubtleVolumeAnimationSlideDown) {
|
||||
self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doShow:(BOOL)animated {
|
||||
if(showing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(runningShowAnimation && !animated) {
|
||||
[self stopAnimations];
|
||||
}
|
||||
|
||||
if(runningShowAnimation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(animated) {
|
||||
// set up for first run, assuming the animation has changed
|
||||
// between instantiation and first showing
|
||||
if(self.animation == SubtleVolumeAnimationSlideDown) {
|
||||
self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height);
|
||||
}
|
||||
|
||||
runningShowAnimation = YES;
|
||||
[UIView animateWithDuration:0.333 animations:^{
|
||||
switch (self.animation) {
|
||||
case SubtleVolumeAnimationNone:
|
||||
break;
|
||||
case SubtleVolumeAnimationFadeIn:
|
||||
self.alpha = 1;
|
||||
break;
|
||||
case SubtleVolumeAnimationSlideDown:
|
||||
self.alpha = 1;
|
||||
self.transform = CGAffineTransformIdentity;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} completion:^(BOOL finished) {
|
||||
showing = YES;
|
||||
runningShowAnimation = NO;
|
||||
}];
|
||||
} else {
|
||||
showing = YES;
|
||||
self.alpha = 1;
|
||||
self.transform = CGAffineTransformIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimations {
|
||||
[self.layer removeAllAnimations];
|
||||
runningHideAnimation = NO;
|
||||
runningShowAnimation = NO;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
|
||||
if ([keyPath isEqual:@"outputVolume"]) {
|
||||
CGFloat value = [change[@"new"] floatValue];
|
||||
[self updateVolume:value animated:self.animatedByDefault];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
+44
-9
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
#import "SubtleVolume.h"
|
||||
#import <GLKit/GLKit.h>
|
||||
#include <cassert>
|
||||
|
||||
@@ -81,8 +82,7 @@ static bool threadStopped = false;
|
||||
__unsafe_unretained ViewController* sharedViewController;
|
||||
static GraphicsContext *graphicsContext;
|
||||
|
||||
@interface ViewController ()
|
||||
{
|
||||
@interface ViewController () {
|
||||
std::map<uint16_t, uint16_t> iCadeToKeyMap;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,12 @@ static GraphicsContext *graphicsContext;
|
||||
|
||||
@end
|
||||
|
||||
@interface ViewController () <SubtleVolumeDelegate> {
|
||||
SubtleVolume *volume;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
-(id) init {
|
||||
@@ -129,6 +135,11 @@ static GraphicsContext *graphicsContext;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)subtleVolume:(SubtleVolume *)volumeView willChange:(CGFloat)value {
|
||||
}
|
||||
- (void)subtleVolume:(SubtleVolume *)volumeView didChange:(CGFloat)value {
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
@@ -197,6 +208,25 @@ static GraphicsContext *graphicsContext;
|
||||
}
|
||||
#endif
|
||||
|
||||
CGFloat margin = 0;
|
||||
CGFloat height = 16;
|
||||
volume = [[SubtleVolume alloc]
|
||||
initWithStyle:SubtleVolumeStylePlain
|
||||
frame:CGRectMake(
|
||||
margin, // X
|
||||
0, // Y
|
||||
self.view.frame.size.width-(margin*2), // width
|
||||
height // height
|
||||
)];
|
||||
|
||||
volume.padding = 7;
|
||||
volume.barTintColor = [UIColor blackColor];
|
||||
volume.barBackgroundColor = [UIColor whiteColor];
|
||||
volume.animation = SubtleVolumeAnimationSlideDown;
|
||||
volume.delegate = self;
|
||||
[self.view addSubview:volume];
|
||||
[self.view bringSubviewToFront:volume];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
NativeInitGraphics(graphicsContext);
|
||||
|
||||
@@ -233,6 +263,11 @@ static GraphicsContext *graphicsContext;
|
||||
if (sharedViewController == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(volume) {
|
||||
[volume removeFromSuperview];
|
||||
volume = nil;
|
||||
}
|
||||
|
||||
Audio_Shutdown();
|
||||
|
||||
@@ -383,13 +418,13 @@ static GraphicsContext *graphicsContext;
|
||||
|
||||
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
for(UITouch* touch in touches)
|
||||
{
|
||||
CGPoint point = [touch locationInView:self.view];
|
||||
NSDictionary* dict = [self touchDictBy:touch];
|
||||
[self touchX:point.x y:point.y code:2 pointerId:[[dict objectForKey:@"index"] intValue]];
|
||||
[self.touches removeObject:dict];
|
||||
}
|
||||
for(UITouch* touch in touches)
|
||||
{
|
||||
CGPoint point = [touch locationInView:self.view];
|
||||
NSDictionary* dict = [self touchDictBy:touch];
|
||||
[self touchX:point.x y:point.y code:2 pointerId:[[dict objectForKey:@"index"] intValue]];
|
||||
[self.touches removeObject:dict];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)bindDefaultFBO
|
||||
|
||||
@@ -543,6 +543,7 @@ static bool TestVertexColor565() {
|
||||
static bool TestVertex8Skin() {
|
||||
VertexDecoderTestHarness dec;
|
||||
|
||||
g_Config.bSoftwareSkinning = true;
|
||||
for (int i = 0; i < 8 * 12; ++i) {
|
||||
gstate.boneMatrix[i] = 0.0f;
|
||||
}
|
||||
@@ -572,6 +573,7 @@ static bool TestVertex8Skin() {
|
||||
static bool TestVertex16Skin() {
|
||||
VertexDecoderTestHarness dec;
|
||||
|
||||
g_Config.bSoftwareSkinning = true;
|
||||
for (int i = 0; i < 8 * 12; ++i) {
|
||||
gstate.boneMatrix[i] = 0.0f;
|
||||
}
|
||||
@@ -601,6 +603,7 @@ static bool TestVertex16Skin() {
|
||||
static bool TestVertexFloatSkin() {
|
||||
VertexDecoderTestHarness dec;
|
||||
|
||||
g_Config.bSoftwareSkinning = true;
|
||||
for (int i = 0; i < 8 * 12; ++i) {
|
||||
gstate.boneMatrix[i] = 0.0f;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user