// Copyright (c) 2015- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #ifdef _WIN32 #define SHADERLOG #endif #include #include "base/logging.h" #include "math/lin/matrix4x4.h" #include "math/math_util.h" #include "math/dataconv.h" #include "util/text/utf8.h" #include "thin3d/VulkanContext.h" #include "Common/Common.h" #include "Core/Config.h" #include "Core/Reporting.h" #include "GPU/Math3D.h" #include "GPU/GPUState.h" #include "GPU/ge_constants.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #include "GPU/Vulkan/FramebufferVulkan.h" #include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" #include "GPU/Vulkan/VertexShaderGeneratorVulkan.h" #include "UI/OnScreenDisplay.h" VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, ShaderID id, const char *code, bool useHWTransform) : vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(nullptr) { source_ = code; std::string errorMessage; std::vector spirv; #ifdef SHADERLOG OutputDebugStringA(code); #endif bool success = GLSLtoSPV(VK_SHADER_STAGE_FRAGMENT_BIT, code, spirv, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); } else { ERROR_LOG(G3D, "Error in shader compilation!"); } ERROR_LOG(G3D, "Messages: %s", errorMessage.c_str()); ERROR_LOG(G3D, "Shader source:\n%s", code); OutputDebugStringA("Messages:\n"); OutputDebugStringA(errorMessage.c_str()); OutputDebugStringA(code); Reporting::ReportMessage("Vulkan error in shader compilation: info: %s / code: %s", errorMessage.c_str(), code); } else { success = vulkan_->CreateShaderModule(spirv, &module_); #ifdef SHADERLOG OutputDebugStringA("OK"); #endif } if (!success) { failed_ = true; return; } else { DEBUG_LOG(G3D, "Compiled shader:\n%s\n", (const char *)code); } } VulkanFragmentShader::~VulkanFragmentShader() { if (module_) { vulkan_->QueueDelete(module_); } } std::string VulkanFragmentShader::GetShaderString(DebugShaderStringType type) const { switch (type) { case SHADER_STRING_SOURCE_CODE: return source_; case SHADER_STRING_SHORT_DESC: return FragmentShaderDesc(id_); default: return "N/A"; } } VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, ShaderID id, const char *code, int vertType, bool useHWTransform) : vulkan_(vulkan), id_(id), failed_(false), useHWTransform_(useHWTransform), module_(nullptr) { source_ = code; std::string errorMessage; std::vector spirv; #ifdef SHADERLOG OutputDebugStringA(code); #endif bool success = GLSLtoSPV(VK_SHADER_STAGE_VERTEX_BIT, code, spirv, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); } else { ERROR_LOG(G3D, "Error in shader compilation!"); } ERROR_LOG(G3D, "Messages: %s", errorMessage.c_str()); ERROR_LOG(G3D, "Shader source:\n%s", code); OutputDebugStringUTF8("Messages:\n"); OutputDebugStringUTF8(errorMessage.c_str()); Reporting::ReportMessage("Vulkan error in shader compilation: info: %s / code: %s", errorMessage.c_str(), code); } else { success = vulkan_->CreateShaderModule(spirv, &module_); #ifdef SHADERLOG OutputDebugStringA("OK"); #endif } if (!success) { failed_ = true; module_ = nullptr; return; } else { DEBUG_LOG(G3D, "Compiled shader:\n%s\n", (const char *)code); } } VulkanVertexShader::~VulkanVertexShader() { if (module_) { vulkan_->QueueDelete(module_); } } std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) const { switch (type) { case SHADER_STRING_SOURCE_CODE: return source_; case SHADER_STRING_SHORT_DESC: return VertexShaderDesc(id_); default: return "N/A"; } } static void ConvertProjMatrixToVulkan(Matrix4x4 &in, bool invertedX, bool invertedY) { // Half pixel offset hack float xoff = 0.5f / gstate_c.curRTRenderWidth; xoff = gstate_c.vpXOffset + (invertedX ? xoff : -xoff); float yoff = 0.5f / gstate_c.curRTRenderHeight; yoff = gstate_c.vpYOffset + (invertedY ? yoff : -yoff); if (invertedX) xoff = -xoff; if (invertedY) yoff = -yoff; const Vec3 trans(xoff, yoff, gstate_c.vpZOffset + 0.5f); const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); in.translateAndScale(trans, scale); } static void ConvertProjMatrixToVulkanThrough(Matrix4x4 &in) { in.translateAndScale(Vec3(0.0f, 0.0f, 0.5f), Vec3(1.0f, 1.0f, 0.5f)); } ShaderManagerVulkan::ShaderManagerVulkan(VulkanContext *vulkan) : vulkan_(vulkan), lastVShader_(nullptr), lastFShader_(nullptr), globalDirty_(0xFFFFFFFF) { codeBuffer_ = new char[16384]; uboAlignment_ = vulkan_->GetPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment; } ShaderManagerVulkan::~ShaderManagerVulkan() { delete[] codeBuffer_; } uint32_t ShaderManagerVulkan::PushBaseBuffer(VulkanPushBuffer *dest) { return dest->PushAligned(&ub_base, sizeof(ub_base), uboAlignment_); } uint32_t ShaderManagerVulkan::PushLightBuffer(VulkanPushBuffer *dest) { return dest->PushAligned(&ub_lights, sizeof(ub_lights), uboAlignment_); } // TODO: Only push half the bone buffer if we only have four bones. uint32_t ShaderManagerVulkan::PushBoneBuffer(VulkanPushBuffer *dest) { return dest->PushAligned(&ub_bones, sizeof(ub_bones), uboAlignment_); } void ShaderManagerVulkan::BaseUpdateUniforms(int dirtyUniforms) { if (dirtyUniforms & DIRTY_TEXENV) { Uint8x3ToFloat4(ub_base.texEnvColor, gstate.texenvcolor); } if (dirtyUniforms & DIRTY_ALPHACOLORREF) { Uint8x3ToFloat4_Alpha(ub_base.alphaColorRef, gstate.getColorTestRef(), (float)gstate.getAlphaTestRef()); } if (dirtyUniforms & DIRTY_ALPHACOLORMASK) { Uint8x3ToInt4(ub_base.colorTestMask, gstate.colortestmask); } if (dirtyUniforms & DIRTY_FOGCOLOR) { Uint8x3ToFloat4(ub_base.fogColor, gstate.fogcolor); } if (dirtyUniforms & DIRTY_STENCILREPLACEVALUE) { Uint8x1ToFloat4(ub_base.stencilReplace, gstate.getStencilTestRef()); } if (dirtyUniforms & DIRTY_SHADERBLEND) { Uint8x3ToFloat4(ub_base.blendFixA, gstate.getFixA()); Uint8x3ToFloat4(ub_base.blendFixB, gstate.getFixB()); } if (dirtyUniforms & DIRTY_TEXCLAMP) { const float invW = 1.0f / (float)gstate_c.curTextureWidth; const float invH = 1.0f / (float)gstate_c.curTextureHeight; const int w = gstate.getTextureWidth(0); const int h = gstate.getTextureHeight(0); const float widthFactor = (float)w * invW; const float heightFactor = (float)h * invH; // First wrap xy, then half texel xy (for clamp.) const float texclamp[4] = { widthFactor, heightFactor, invW * 0.5f, invH * 0.5f, }; const float texclampoff[2] = { gstate_c.curTextureXOffset * invW, gstate_c.curTextureYOffset * invH, }; CopyFloat4(ub_base.texClamp, texclamp); CopyFloat2(ub_base.texClampOffset, texclampoff); } // Update any dirty uniforms before we draw if (dirtyUniforms & DIRTY_PROJMATRIX) { if (gstate.isModeThrough()) { Matrix4x4 proj_through; proj_through.setOrtho(0.0f, gstate_c.curRTWidth, 0, gstate_c.curRTHeight, 0, 1); ConvertProjMatrixToVulkanThrough(proj_through); CopyMatrix4x4(ub_base.proj, proj_through.getReadPtr()); } else { Matrix4x4 flippedMatrix; memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float)); const bool invertedY = gstate_c.vpHeight < 0; if (invertedY) { flippedMatrix[1] = -flippedMatrix[1]; flippedMatrix[5] = -flippedMatrix[5]; flippedMatrix[9] = -flippedMatrix[9]; flippedMatrix[13] = -flippedMatrix[13]; } const bool invertedX = gstate_c.vpWidth < 0; if (invertedX) { flippedMatrix[0] = -flippedMatrix[0]; flippedMatrix[4] = -flippedMatrix[4]; flippedMatrix[8] = -flippedMatrix[8]; flippedMatrix[12] = -flippedMatrix[12]; } ConvertProjMatrixToVulkan(flippedMatrix, invertedX, invertedY); CopyMatrix4x4(ub_base.proj, flippedMatrix.getReadPtr()); } } // Transform if (dirtyUniforms & DIRTY_WORLDMATRIX) { ConvertMatrix4x3To4x4(ub_base.world, gstate.worldMatrix); } if (dirtyUniforms & DIRTY_VIEWMATRIX) { ConvertMatrix4x3To4x4(ub_base.view, gstate.viewMatrix); } if (dirtyUniforms & DIRTY_TEXMATRIX) { ConvertMatrix4x3To4x4(ub_base.tex, gstate.tgenMatrix); } if (dirtyUniforms & DIRTY_FOGCOEF) { float fogcoef[2] = { getFloat24(gstate.fog1), getFloat24(gstate.fog2), }; if (my_isinf(fogcoef[1])) { // not really sure what a sensible value might be. fogcoef[1] = fogcoef[1] < 0.0f ? -10000.0f : 10000.0f; } else if (my_isnan(fogcoef[1])) { // Workaround for https://github.com/hrydgard/ppsspp/issues/5384#issuecomment-38365988 // Just put the fog far away at a large finite distance. // Infinities and NaNs are rather unpredictable in shaders on many GPUs // so it's best to just make it a sane calculation. fogcoef[0] = 100000.0f; fogcoef[1] = 1.0f; } #ifndef MOBILE_DEVICE else if (my_isnanorinf(fogcoef[1]) || my_isnanorinf(fogcoef[0])) { ERROR_LOG_REPORT_ONCE(fognan, G3D, "Unhandled fog NaN/INF combo: %f %f", fogcoef[0], fogcoef[1]); } #endif CopyFloat2(ub_base.fogCoef, fogcoef); } // Texturing if (dirtyUniforms & DIRTY_UVSCALEOFFSET) { const float invW = 1.0f / (float)gstate_c.curTextureWidth; const float invH = 1.0f / (float)gstate_c.curTextureHeight; const int w = gstate.getTextureWidth(0); const int h = gstate.getTextureHeight(0); const float widthFactor = (float)w * invW; const float heightFactor = (float)h * invH; float uvscaleoff[4]; switch (gstate.getUVGenMode()) { case GE_TEXMAP_TEXTURE_COORDS: // Not sure what GE_TEXMAP_UNKNOWN is, but seen in Riviera. Treating the same as GE_TEXMAP_TEXTURE_COORDS works. case GE_TEXMAP_UNKNOWN: uvscaleoff[0] = gstate_c.uv.uScale * widthFactor; uvscaleoff[1] = gstate_c.uv.vScale * heightFactor; uvscaleoff[2] = gstate_c.uv.uOff * widthFactor; uvscaleoff[3] = gstate_c.uv.vOff * heightFactor; break; // These two work the same whether or not we prescale UV. case GE_TEXMAP_TEXTURE_MATRIX: // We cannot bake the UV coord scale factor in here, as we apply a matrix multiplication // before this is applied, and the matrix multiplication may contain translation. In this case // the translation will be scaled which breaks faces in Hexyz Force for example. // So I've gone back to applying the scale factor in the shader. uvscaleoff[0] = widthFactor; uvscaleoff[1] = heightFactor; uvscaleoff[2] = 0.0f; uvscaleoff[3] = 0.0f; break; case GE_TEXMAP_ENVIRONMENT_MAP: // In this mode we only use uvscaleoff to scale to the texture size. uvscaleoff[0] = widthFactor; uvscaleoff[1] = heightFactor; uvscaleoff[2] = 0.0f; uvscaleoff[3] = 0.0f; break; default: ERROR_LOG_REPORT(G3D, "Unexpected UV gen mode: %d", gstate.getUVGenMode()); } CopyFloat4(ub_base.uvScaleOffset, uvscaleoff); } if (dirtyUniforms & DIRTY_DEPTHRANGE) { float viewZScale = gstate.getViewportZScale(); float viewZCenter = gstate.getViewportZCenter(); float viewZInvScale; // We had to scale and translate Z to account for our clamped Z range. // Therefore, we also need to reverse this to round properly. // // Example: scale = 65535.0, center = 0.0 // Resulting range = -65535 to 65535, clamped to [0, 65535] // gstate_c.vpDepthScale = 2.0f // gstate_c.vpZOffset = -1.0f // // The projection already accounts for those, so we need to reverse them. // // Additionally, D3D9 uses a range from [0, 1]. We double and move the center. viewZScale *= (1.0f / gstate_c.vpDepthScale) * 2.0f; viewZCenter -= 65535.0f * gstate_c.vpZOffset + 32768.5f; if (viewZScale != 0.0) { viewZInvScale = 1.0f / viewZScale; } else { viewZInvScale = 0.0; } float data[4] = { viewZScale, viewZCenter, viewZCenter, viewZInvScale }; CopyFloat4(ub_base.depthRange, data); } } void ShaderManagerVulkan::LightUpdateUniforms(int dirtyUniforms) { // Lighting if (dirtyUniforms & DIRTY_AMBIENT) { Uint8x3ToFloat4_AlphaUint8(ub_lights.ambientColor, gstate.ambientcolor, gstate.getAmbientA()); } if (dirtyUniforms & DIRTY_MATAMBIENTALPHA) { // Note - this one is not in lighting but in transformCommon as it has uses beyond lighting Uint8x3ToFloat4_AlphaUint8(ub_base.matAmbient, gstate.materialambient, gstate.getMaterialAmbientA()); } if (dirtyUniforms & DIRTY_MATDIFFUSE) { Uint8x3ToFloat4(ub_lights.materialDiffuse, gstate.materialdiffuse); } if (dirtyUniforms & DIRTY_MATEMISSIVE) { Uint8x3ToFloat4(ub_lights.materialEmissive, gstate.materialemissive); } if (dirtyUniforms & DIRTY_MATSPECULAR) { Uint8x3ToFloat4_Alpha(ub_lights.materialSpecular, gstate.materialspecular, getFloat24(gstate.materialspecularcoef)); } for (int i = 0; i < 4; i++) { if (dirtyUniforms & (DIRTY_LIGHT0 << i)) { if (gstate.isDirectionalLight(i)) { // Prenormalize float x = getFloat24(gstate.lpos[i * 3 + 0]); float y = getFloat24(gstate.lpos[i * 3 + 1]); float z = getFloat24(gstate.lpos[i * 3 + 2]); float len = sqrtf(x*x + y*y + z*z); if (len == 0.0f) len = 1.0f; else len = 1.0f / len; float vec[3] = { x * len, y * len, z * len }; CopyFloat3To4(ub_lights.lpos[i], vec); } else { ExpandFloat24x3ToFloat4(ub_lights.lpos[i], &gstate.lpos[i * 3]); } ExpandFloat24x3ToFloat4(ub_lights.ldir[i], &gstate.ldir[i * 3]); ExpandFloat24x3ToFloat4(ub_lights.latt[i], &gstate.latt[i * 3]); CopyFloat1To4(ub_lights.lightAngle[i], getFloat24(gstate.lcutoff[i])); CopyFloat1To4(ub_lights.lightSpotCoef[i], getFloat24(gstate.lconv[i])); Uint8x3ToFloat4(ub_lights.lightAmbient[i], gstate.lcolor[i * 3]); Uint8x3ToFloat4(ub_lights.lightDiffuse[i], gstate.lcolor[i * 3 + 1]); Uint8x3ToFloat4(ub_lights.lightSpecular[i], gstate.lcolor[i * 3 + 2]); } } } void ShaderManagerVulkan::BoneUpdateUniforms(int dirtyUniforms) { for (int i = 0; i < 8; i++) { if (dirtyUniforms & (DIRTY_BONEMATRIX0 << i)) { ConvertMatrix4x3To4x4(ub_bones.bones[i], gstate.boneMatrix + 12 * i); } } } void ShaderManagerVulkan::Clear() { for (auto iter = fsCache_.begin(); iter != fsCache_.end(); ++iter) { delete iter->second; } for (auto iter = vsCache_.begin(); iter != vsCache_.end(); ++iter) { delete iter->second; } fsCache_.clear(); vsCache_.clear(); lastFSID_.clear(); lastVSID_.clear(); DirtyShader(); DirtyUniform(0xFFFFFFFF); } void ShaderManagerVulkan::ClearCache(bool deleteThem) { Clear(); } void ShaderManagerVulkan::DirtyShader() { // Forget the last shader ID lastFSID_.clear(); lastVSID_.clear(); lastVShader_ = nullptr; lastFShader_ = nullptr; } void ShaderManagerVulkan::DirtyLastShader() { // disables vertex arrays lastVShader_ = nullptr; lastFShader_ = nullptr; } void ShaderManagerVulkan::UpdateUniforms() { if (globalDirty_) { BaseUpdateUniforms(globalDirty_); LightUpdateUniforms(globalDirty_); BoneUpdateUniforms(globalDirty_); globalDirty_ = 0; } } void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, bool useHWTransform) { ShaderID VSID; ShaderID FSID; ComputeVertexShaderID(&VSID, vertType, useHWTransform); ComputeFragmentShaderID(&FSID); // Just update uniforms if this is the same shader as last time. if (lastVShader_ != nullptr && lastFShader_ != nullptr && VSID == lastVSID_ && FSID == lastFSID_) { *vshader = lastVShader_; *fshader = lastFShader_; // Already all set, no need to look up in shader maps. return; } VSCache::iterator vsIter = vsCache_.find(VSID); VulkanVertexShader *vs; if (vsIter == vsCache_.end()) { // Vertex shader not in cache. Let's compile it. GenerateVulkanGLSLVertexShader(VSID, codeBuffer_); vs = new VulkanVertexShader(vulkan_, VSID, codeBuffer_, vertType, useHWTransform); vsCache_[VSID] = vs; } else { vs = vsIter->second; } lastVSID_ = VSID; FSCache::iterator fsIter = fsCache_.find(FSID); VulkanFragmentShader *fs; if (fsIter == fsCache_.end()) { // Fragment shader not in cache. Let's compile it. GenerateVulkanGLSLFragmentShader(FSID, codeBuffer_); fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_, useHWTransform); fsCache_[FSID] = fs; } else { fs = fsIter->second; } lastFSID_ = FSID; lastVShader_ = vs; lastFShader_ = fs; *vshader = vs; *fshader = fs; } std::vector ShaderManagerVulkan::DebugGetShaderIDs(DebugShaderType type) { std::string id; std::vector ids; switch (type) { case SHADER_TYPE_VERTEX: { for (auto iter : vsCache_) { iter.first.ToString(&id); ids.push_back(id); } } break; case SHADER_TYPE_FRAGMENT: { for (auto iter : fsCache_) { iter.first.ToString(&id); ids.push_back(id); } } break; } return ids; } std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) { ShaderID shaderId; shaderId.FromString(id); switch (type) { case SHADER_TYPE_VERTEX: { auto iter = vsCache_.find(shaderId); if (iter == vsCache_.end()) { return ""; } return iter->second->GetShaderString(stringType); } case SHADER_TYPE_FRAGMENT: { auto iter = fsCache_.find(shaderId); if (iter == fsCache_.end()) { return ""; } return iter->second->GetShaderString(stringType); } default: return "N/A"; } }