diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..f1af20d665 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,53 @@ +# libretro CI +variables: + GIT_SUBMODULE_STRATEGY: normal + +.core-defs: + variables: + CORENAME: ppsspp + CORE_ARGS: -DLIBRETRO=ON + +.linux-defs: + variables: + EXTRA_PATH: lib + +include: + - template: Jobs/Code-Quality.gitlab-ci.yml + - project: 'libretro-infrastructure/ci-templates' + file: '/android-cmake.yml' + - project: 'libretro-infrastructure/ci-templates' + file: '/linux-cmake.yml' + +stages: + - build-prepare + - build-shared + - build-static + - test + +#Desktop +libretro-build-linux-x86_64: + extends: + - .libretro-linux-cmake-x86_64 + - .core-defs + - .linux-defs + +# Android +libretro-build-android-armeabi-v7a: + extends: + - .libretro-android-cmake-armeabi-v7a + - .core-defs + +libretro-build-android-arm64-v8a: + extends: + - .libretro-android-cmake-arm64-v8a + - .core-defs + +libretro-build-android-x86_64: + extends: + - .libretro-android-cmake-x86_64 + - .core-defs + +libretro-build-android-x86: + extends: + - .libretro-android-cmake-x86 + - .core-defs diff --git a/CMakeLists.txt b/CMakeLists.txt index 431ee28228..28e02a9123 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,6 +496,12 @@ add_library(Common STATIC Common/GPU/thin3d.cpp Common/GPU/thin3d.h Common/GPU/thin3d_create.h + Common/GPU/Shader.cpp + Common/GPU/Shader.h + Common/GPU/ShaderWriter.cpp + Common/GPU/ShaderWriter.h + Common/GPU/ShaderTranslation.h + Common/GPU/ShaderTranslation.cpp Common/GPU/OpenGL/GLCommon.h Common/GPU/OpenGL/GLDebugLog.cpp Common/GPU/OpenGL/GLDebugLog.h @@ -1235,8 +1241,6 @@ set(GPU_GLES GPU/GLES/DepthBufferGLES.cpp GPU/GLES/GPU_GLES.cpp GPU/GLES/GPU_GLES.h - GPU/GLES/FragmentShaderGeneratorGLES.cpp - GPU/GLES/FragmentShaderGeneratorGLES.h GPU/GLES/FragmentTestCacheGLES.cpp GPU/GLES/FragmentTestCacheGLES.h GPU/GLES/FramebufferManagerGLES.cpp @@ -1252,8 +1256,6 @@ set(GPU_GLES GPU/GLES/TextureScalerGLES.h GPU/GLES/DrawEngineGLES.cpp GPU/GLES/DrawEngineGLES.h - GPU/GLES/VertexShaderGeneratorGLES.cpp - GPU/GLES/VertexShaderGeneratorGLES.h ) set(GPU_VULKAN @@ -1263,8 +1265,6 @@ set(GPU_VULKAN GPU/Vulkan/DebugVisVulkan.h GPU/Vulkan/DrawEngineVulkan.cpp GPU/Vulkan/DrawEngineVulkan.h - GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp - GPU/Vulkan/FragmentShaderGeneratorVulkan.h GPU/Vulkan/FramebufferManagerVulkan.cpp GPU/Vulkan/FramebufferManagerVulkan.h GPU/Vulkan/GPU_Vulkan.cpp @@ -1280,8 +1280,6 @@ set(GPU_VULKAN GPU/Vulkan/TextureCacheVulkan.h GPU/Vulkan/TextureScalerVulkan.cpp GPU/Vulkan/TextureScalerVulkan.h - GPU/Vulkan/VertexShaderGeneratorVulkan.cpp - GPU/Vulkan/VertexShaderGeneratorVulkan.h GPU/Vulkan/VulkanUtil.cpp GPU/Vulkan/VulkanUtil.h ) @@ -1291,8 +1289,6 @@ set(GPU_D3D9 GPU/Directx9/DepalettizeShaderDX9.h GPU/Directx9/DrawEngineDX9.cpp GPU/Directx9/DrawEngineDX9.h - GPU/Directx9/PixelShaderGeneratorDX9.cpp - GPU/Directx9/PixelShaderGeneratorDX9.h GPU/Directx9/FramebufferManagerDX9.cpp GPU/Directx9/FramebufferManagerDX9.h GPU/Directx9/GPU_DX9.cpp @@ -1306,8 +1302,8 @@ set(GPU_D3D9 GPU/Directx9/TextureCacheDX9.h GPU/Directx9/TextureScalerDX9.cpp GPU/Directx9/TextureScalerDX9.h - GPU/Directx9/VertexShaderGeneratorDX9.cpp - GPU/Directx9/VertexShaderGeneratorDX9.h + GPU/Directx9/VertexShaderGeneratorHLSL.cpp + GPU/GLES/VertexShaderGeneratorGLES.h ) set(GPU_D3D11 @@ -1315,8 +1311,6 @@ set(GPU_D3D11 GPU/D3D11/DepalettizeShaderD3D11.h GPU/D3D11/DrawEngineD3D11.cpp GPU/D3D11/DrawEngineD3D11.h - GPU/D3D11/FragmentShaderGeneratorD3D11.cpp - GPU/D3D11/FragmentShaderGeneratorD3D11.h GPU/D3D11/FramebufferManagerD3D11.cpp GPU/D3D11/FramebufferManagerD3D11.h GPU/D3D11/GPU_D3D11.cpp @@ -1332,8 +1326,6 @@ set(GPU_D3D11 GPU/D3D11/TextureCacheD3D11.h GPU/D3D11/TextureScalerD3D11.cpp GPU/D3D11/TextureScalerD3D11.h - GPU/D3D11/VertexShaderGeneratorD3D11.cpp - GPU/D3D11/VertexShaderGeneratorD3D11.h ) # We build Vulkan even on Apple to avoid annoying build differences. @@ -1350,6 +1342,10 @@ set(GPU_SOURCES ${GPU_NEON} GPU/Common/DepalettizeShaderCommon.cpp GPU/Common/DepalettizeShaderCommon.h + GPU/Common/FragmentShaderGenerator.cpp + GPU/Common/FragmentShaderGenerator.h + GPU/Common/VertexShaderGenerator.cpp + GPU/Common/VertexShaderGenerator.h GPU/Common/FramebufferManagerCommon.cpp GPU/Common/FramebufferManagerCommon.h GPU/Common/GPUDebugInterface.cpp @@ -1360,14 +1356,14 @@ set(GPU_SOURCES GPU/Common/DrawEngineCommon.h GPU/Common/PresentationCommon.cpp GPU/Common/PresentationCommon.h + GPU/Common/ReinterpretFramebuffer.cpp + GPU/Common/ReinterpretFramebuffer.h GPU/Common/ShaderId.cpp GPU/Common/ShaderId.h GPU/Common/ShaderUniforms.cpp GPU/Common/ShaderUniforms.h GPU/Common/ShaderCommon.cpp GPU/Common/ShaderCommon.h - GPU/Common/ShaderTranslation.cpp - GPU/Common/ShaderTranslation.h GPU/Common/SplineCommon.cpp GPU/Common/SplineCommon.h GPU/Common/StencilCommon.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 0bb5e7ef50..b58a19d289 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -386,6 +386,7 @@ + @@ -428,6 +429,9 @@ + + + @@ -749,6 +753,9 @@ + + + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 5cc1151375..3a7dbc7980 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -375,6 +375,18 @@ Data\Collections + + GPU + + + GPU + + + Data\Collections + + + GPU + @@ -723,6 +735,15 @@ Render\Text + + GPU + + + GPU + + + GPU + diff --git a/Common/Data/Collections/Slice.h b/Common/Data/Collections/Slice.h new file mode 100644 index 0000000000..a58b902481 --- /dev/null +++ b/Common/Data/Collections/Slice.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +// Like a const begin/end pair, just more convenient to use (and can only be used for linear array data). +// Inspired by Rust's slices and Google's StringPiece. +template +struct Slice { + // View some memory as a slice. + Slice(const T *data, size_t size) : data_(data), size_(size) {} + + // Intentionally non-explicit. + // View a const array as a slice. + template + Slice(const T(&data)[N]) : data_(data), size_(N) {} + + // Intentionally non-explicit. + // View a const array as a slice. + Slice(const std::vector &data) : data_(data.data()), size_(data.size()) {} + + const T &operator[](size_t index) const { + return data_[index]; + } + + size_t size() const { + return size_; + } + + // "Iterators" + const T *begin() const { + return data_; + } + const T *end() const { + return data_ + size_; + } + + static Slice empty() { + return Slice(nullptr, 0); + } + + bool is_empty() const { + return size_ == 0; + } + +private: + const T *data_; + size_t size_; +}; diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp index 8e3bb0f05d..f3c2b1e223 100644 --- a/Common/Data/Encoding/Utf8.cpp +++ b/Common/Data/Encoding/Utf8.cpp @@ -450,6 +450,12 @@ void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const std::string &sou MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, dest, std::min((int)destSize, size)); } +void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const char *source) { + int len = (int)strlen(source) + 1; // include trailing zero + int size = (int)MultiByteToWideChar(CP_UTF8, 0, source, len, NULL, 0); + MultiByteToWideChar(CP_UTF8, 0, source, len, dest, std::min((int)destSize, size)); +} + std::wstring ConvertUTF8ToWString(const std::string &source) { int len = (int)source.size(); int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, NULL, 0); diff --git a/Common/Data/Encoding/Utf8.h b/Common/Data/Encoding/Utf8.h index 9d04a788a0..b954ef5371 100644 --- a/Common/Data/Encoding/Utf8.h +++ b/Common/Data/Encoding/Utf8.h @@ -83,6 +83,7 @@ bool UTF8StringHasNonASCII(const char *utf8string); std::string ConvertWStringToUTF8(const std::wstring &wstr); std::string ConvertWStringToUTF8(const wchar_t *wstr); void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const std::string &source); +void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const char *source); std::wstring ConvertUTF8ToWString(const std::string &source); #else diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index f822d7e087..9794c08c94 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -233,6 +233,9 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de hWnd_(hWnd), deviceList_(deviceList) { + // We no longer support Windows Phone. + _assert_(featureLevel_ >= D3D_FEATURE_LEVEL_9_3); + // Seems like a fair approximation... caps_.dualSourceBlend = featureLevel_ >= D3D_FEATURE_LEVEL_10_0; caps_.depthClampSupported = featureLevel_ >= D3D_FEATURE_LEVEL_10_0; @@ -291,6 +294,8 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de packDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; hr = device_->CreateTexture2D(&packDesc, nullptr, &packTexture_); _assert_(SUCCEEDED(hr)); + + shaderLanguageDesc_.Init(HLSL_D3D11); } D3D11DrawContext::~D3D11DrawContext() { @@ -659,6 +664,8 @@ InputLayout *D3D11DrawContext::CreateInputLayout(const InputLayoutDesc &desc) { return inputLayout; } +class D3D11ShaderModule; + class D3D11Pipeline : public Pipeline { public: ~D3D11Pipeline() { @@ -675,7 +682,7 @@ public: if (dynamicUniforms) dynamicUniforms->Release(); } - bool RequiresBuffer() { + bool RequiresBuffer() override { return true; } @@ -689,6 +696,8 @@ public: ID3D11GeometryShader *gs = nullptr; D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; + std::vector shaderModules; + size_t dynamicUniformsSize = 0; ID3D11Buffer *dynamicUniforms = nullptr; }; @@ -889,16 +898,14 @@ ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLang std::string errors; const char *target = nullptr; switch (stage) { - case ShaderStage::FRAGMENT: target = fragmentModel; break; - case ShaderStage::VERTEX: target = vertexModel; break; - case ShaderStage::GEOMETRY: + case ShaderStage::Fragment: target = fragmentModel; break; + case ShaderStage::Vertex: target = vertexModel; break; + case ShaderStage::Geometry: if (!geometryModel) return nullptr; target = geometryModel; break; - case ShaderStage::COMPUTE: - case ShaderStage::CONTROL: - case ShaderStage::EVALUATION: + case ShaderStage::Compute: default: Crash(); break; @@ -932,13 +939,13 @@ ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLang module->stage = stage; module->byteCode_ = std::vector(data, data + dataSize); switch (stage) { - case ShaderStage::VERTEX: + case ShaderStage::Vertex: result = device_->CreateVertexShader(data, dataSize, nullptr, &module->vs); break; - case ShaderStage::FRAGMENT: + case ShaderStage::Fragment: result = device_->CreatePixelShader(data, dataSize, nullptr, &module->ps); break; - case ShaderStage::GEOMETRY: + case ShaderStage::Geometry: result = device_->CreateGeometryShader(data, dataSize, nullptr, &module->gs); break; default: @@ -963,7 +970,9 @@ Pipeline *D3D11DrawContext::CreateGraphicsPipeline(const PipelineDesc &desc) { dPipeline->raster = (D3D11RasterState *)desc.raster; dPipeline->blend->AddRef(); dPipeline->depth->AddRef(); - dPipeline->input->AddRef(); + if (dPipeline->input) { + dPipeline->input->AddRef(); + } dPipeline->raster->AddRef(); dPipeline->topology = primToD3D11[(int)desc.prim]; if (desc.uniformDesc) { @@ -980,21 +989,24 @@ Pipeline *D3D11DrawContext::CreateGraphicsPipeline(const PipelineDesc &desc) { std::vector shaders; D3D11ShaderModule *vshader = nullptr; for (auto iter : desc.shaders) { + iter->AddRef(); + D3D11ShaderModule *module = (D3D11ShaderModule *)iter; shaders.push_back(module); switch (module->GetStage()) { - case ShaderStage::VERTEX: + case ShaderStage::Vertex: vshader = module; dPipeline->vs = module->vs; break; - case ShaderStage::FRAGMENT: + case ShaderStage::Fragment: dPipeline->ps = module->ps; break; - case ShaderStage::GEOMETRY: + case ShaderStage::Geometry: dPipeline->gs = module->gs; break; } } + dPipeline->shaderModules = shaders; if (!vshader) { // No vertex shader - no graphics @@ -1003,11 +1015,15 @@ Pipeline *D3D11DrawContext::CreateGraphicsPipeline(const PipelineDesc &desc) { } // Can finally create the input layout - auto &inputDesc = dPipeline->input->desc; - const std::vector &elements = dPipeline->input->elements; - HRESULT hr = device_->CreateInputLayout(elements.data(), (UINT)elements.size(), vshader->byteCode_.data(), vshader->byteCode_.size(), &dPipeline->il); - if (!SUCCEEDED(hr)) { - Crash(); + if (dPipeline->input) { + auto &inputDesc = dPipeline->input->desc; + const std::vector &elements = dPipeline->input->elements; + HRESULT hr = device_->CreateInputLayout(elements.data(), (UINT)elements.size(), vshader->byteCode_.data(), vshader->byteCode_.size(), &dPipeline->il); + if (!SUCCEEDED(hr)) { + Crash(); + } + } else { + dPipeline->il = nullptr; } return dPipeline; } @@ -1078,8 +1094,10 @@ void D3D11DrawContext::ApplyCurrentState() { curTopology_ = curPipeline_->topology; } - int numVBs = (int)curPipeline_->input->strides.size(); - context_->IASetVertexBuffers(0, 1, nextVertexBuffers_, (UINT *)curPipeline_->input->strides.data(), (UINT *)nextVertexBufferOffsets_); + if (curPipeline_->input) { + int numVBs = (int)curPipeline_->input->strides.size(); + context_->IASetVertexBuffers(0, numVBs, nextVertexBuffers_, (UINT *)curPipeline_->input->strides.data(), (UINT *)nextVertexBufferOffsets_); + } if (dirtyIndexBuffer_) { context_->IASetIndexBuffer(nextIndexBuffer_, DXGI_FORMAT_R16_UINT, nextIndexBufferOffset_); dirtyIndexBuffer_ = false; @@ -1204,7 +1222,10 @@ uint32_t D3D11DrawContext::GetDataFormatSupport(DataFormat fmt) const { // A D3D11Framebuffer is a D3D11Framebuffer plus all the textures it owns. class D3D11Framebuffer : public Framebuffer { public: - D3D11Framebuffer() {} + D3D11Framebuffer(int width, int height) { + width_ = width; + height_ = height; + } ~D3D11Framebuffer() { if (colorTex) colorTex->Release(); @@ -1212,17 +1233,18 @@ public: colorRTView->Release(); if (colorSRView) colorSRView->Release(); + if (depthSRView) + depthSRView->Release(); if (depthStencilTex) depthStencilTex->Release(); if (depthStencilRTView) depthStencilRTView->Release(); } - int width; - int height; ID3D11Texture2D *colorTex = nullptr; ID3D11RenderTargetView *colorRTView = nullptr; ID3D11ShaderResourceView *colorSRView = nullptr; + ID3D11ShaderResourceView *depthSRView = nullptr; DXGI_FORMAT colorFormat = DXGI_FORMAT_UNKNOWN; ID3D11Texture2D *depthStencilTex = nullptr; @@ -1232,9 +1254,7 @@ public: Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) { HRESULT hr; - D3D11Framebuffer *fb = new D3D11Framebuffer(); - fb->width = desc.width; - fb->height = desc.height; + D3D11Framebuffer *fb = new D3D11Framebuffer(desc.width, desc.height); if (desc.numColorAttachments) { fb->colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM; D3D11_TEXTURE2D_DESC descColor{}; @@ -1273,11 +1293,11 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) { descDepth.Height = desc.height; descDepth.MipLevels = 1; descDepth.ArraySize = 1; - descDepth.Format = fb->depthStencilFormat; + descDepth.Format = DXGI_FORMAT_R24G8_TYPELESS; // so we can create an R24X8 view of it. descDepth.SampleDesc.Count = 1; descDepth.SampleDesc.Quality = 0; descDepth.Usage = D3D11_USAGE_DEFAULT; - descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL; + descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; descDepth.CPUAccessFlags = 0; descDepth.MiscFlags = 0; hr = device_->CreateTexture2D(&descDepth, nullptr, &fb->depthStencilTex); @@ -1286,7 +1306,7 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) { return nullptr; } D3D11_DEPTH_STENCIL_VIEW_DESC descDSV{}; - descDSV.Format = descDepth.Format; + descDSV.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; descDSV.Texture2D.MipSlice = 0; hr = device_->CreateDepthStencilView(fb->depthStencilTex, &descDSV, &fb->depthStencilRTView); @@ -1294,6 +1314,17 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) { delete fb; return nullptr; } + + D3D11_SHADER_RESOURCE_VIEW_DESC depthViewDesc{}; + depthViewDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + depthViewDesc.Texture2D.MostDetailedMip = 0; + depthViewDesc.Texture2D.MipLevels = 1; + depthViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + hr = device_->CreateShaderResourceView(fb->depthStencilTex, &depthViewDesc, &fb->depthSRView); + if (FAILED(hr)) { + WARN_LOG(G3D, "Failed to create SRV for depth buffer."); + fb->depthSRView = nullptr; + } } return fb; @@ -1381,7 +1412,7 @@ void D3D11DrawContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x } // TODO: Check for level too! - if (width == src->width && width == dst->width && height == src->height && height == dst->height && x == 0 && y == 0 && z == 0 && dstX == 0 && dstY == 0 && dstZ == 0) { + if (width == src->Width() && width == dst->Width() && height == src->Height() && height == dst->Height() && x == 0 && y == 0 && z == 0 && dstX == 0 && dstY == 0 && dstZ == 0) { // Don't need to specify region. This might be faster, too. context_->CopyResource(dstTex, srcTex); return; @@ -1400,11 +1431,11 @@ void D3D11DrawContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x dstY -= y; y = 0; } - if (x + width > src->width) { - width = src->width - x; + if (x + width > src->Width()) { + width = src->Width() - x; } - if (y + height > src->height) { - height = src->height - y; + if (y + height > src->Height()) { + height = src->Height() - y; } D3D11_BOX srcBox{ (UINT)x, (UINT)y, (UINT)z, (UINT)(x + width), (UINT)(y + height), (UINT)(z + depth) }; context_->CopySubresourceRegion(dstTex, dstLevel, dstX, dstY, dstZ, srcTex, level, &srcBox); @@ -1426,11 +1457,11 @@ bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channel _assert_(fb->colorFormat == DXGI_FORMAT_R8G8B8A8_UNORM); // TODO: Figure out where the badness really comes from. - if (bx + bw > fb->width) { - bw -= (bx + bw) - fb->width; + if (bx + bw > fb->Width()) { + bw -= (bx + bw) - fb->Width(); } - if (by + bh > fb->height) { - bh -= (by + bh) - fb->height; + if (by + bh > fb->Height()) { + bh -= (by + bh) - fb->Height(); } } @@ -1550,8 +1581,8 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren context_->OMSetRenderTargets(1, &fb->colorRTView, fb->depthStencilRTView); curRenderTargetView_ = fb->colorRTView; curDepthStencilView_ = fb->depthStencilRTView; - curRTWidth_ = fb->width; - curRTHeight_ = fb->height; + curRTWidth_ = fb->Width(); + curRTHeight_ = fb->Height(); } } else { if (curRenderTargetView_ == bbRenderTargetView_ && curDepthStencilView_ == bbDepthStencilView_) { @@ -1587,7 +1618,18 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren // color must be 0, for now. void D3D11DrawContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) { D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo; - context_->PSSetShaderResources(binding, 1, &fb->colorSRView); + switch (channelBit) { + case FBChannel::FB_COLOR_BIT: + context_->PSSetShaderResources(binding, 1, &fb->colorSRView); + break; + case FBChannel::FB_DEPTH_BIT: + if (fb->depthSRView) { + context_->PSSetShaderResources(binding, 1, &fb->depthSRView); + } + break; + default: + break; + } } uintptr_t D3D11DrawContext::GetFramebufferAPITexture(Framebuffer *fbo, int channelBit, int attachment) { @@ -1608,8 +1650,8 @@ uintptr_t D3D11DrawContext::GetFramebufferAPITexture(Framebuffer *fbo, int chann void D3D11DrawContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo; if (fb) { - *w = fb->width; - *h = fb->height; + *w = fb->Width(); + *h = fb->Height(); } else { *w = bbWidth_; *h = bbHeight_; diff --git a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp index b54bb215c1..e8585f15f3 100644 --- a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp +++ b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp @@ -1,18 +1,18 @@ -#ifdef _WIN32 - #include "ppsspp_config.h" +#ifdef _WIN32 + #include "Common/CommonWindows.h" #include "Common/GPU/D3D9/D3DCompilerLoader.h" #include "Common/GPU/D3D9/D3D9ShaderCompiler.h" #include "Common/CommonFuncs.h" #include "Common/SysError.h" +#include "Common/Log.h" +#include "Common/StringUtils.h" struct ID3DXConstantTable; -namespace DX9 { - -bool CompilePixelShader(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DPIXELSHADER9 *pShader, ID3DXConstantTable **pShaderTable, std::string &errorMessage) { +LPD3DBLOB CompileShaderToByteCodeD3D9(const char *code, const char *target, std::string *errorMessage) { LPD3DBLOB pShaderCode = nullptr; LPD3DBLOB pErrorMsg = nullptr; @@ -23,77 +23,58 @@ bool CompilePixelShader(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DPI nullptr, nullptr, "main", - "ps_2_0", + target, 0, 0, &pShaderCode, &pErrorMsg); if (pErrorMsg) { - errorMessage = (CHAR *)pErrorMsg->GetBufferPointer(); + *errorMessage = std::string((CHAR *)pErrorMsg->GetBufferPointer()); + + OutputDebugStringUTF8(LineNumberString(std::string(code)).c_str()); + OutputDebugStringUTF8(errorMessage->c_str()); + pErrorMsg->Release(); - } else if (FAILED(hr)) { - errorMessage = GetStringErrorMsg(hr); - } else { - errorMessage = ""; - } - - if (FAILED(hr) || !pShaderCode) { - if (pShaderCode) + if (pShaderCode) { pShaderCode->Release(); - return false; + pShaderCode = nullptr; + } + } else if (FAILED(hr)) { + *errorMessage = GetStringErrorMsg(hr); + if (pShaderCode) { + pShaderCode->Release(); + pShaderCode = nullptr; + } + } else { + *errorMessage = ""; } - // Create pixel shader. - device->CreatePixelShader( (DWORD*)pShaderCode->GetBufferPointer(), - pShader ); - - pShaderCode->Release(); - - return true; + return pShaderCode; } -bool CompileVertexShader(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DVERTEXSHADER9 *pShader, ID3DXConstantTable **pShaderTable, std::string &errorMessage) { - LPD3DBLOB pShaderCode = nullptr; - LPD3DBLOB pErrorMsg = nullptr; - - // Compile pixel shader. - HRESULT hr = dyn_D3DCompile(code, - (UINT)strlen(code), - nullptr, - nullptr, - nullptr, - "main", - "vs_2_0", - 0, - 0, - &pShaderCode, - &pErrorMsg); - - if (pErrorMsg) { - errorMessage = (CHAR *)pErrorMsg->GetBufferPointer(); - pErrorMsg->Release(); - } else if (FAILED(hr)) { - errorMessage = GetStringErrorMsg(hr); +bool CompilePixelShaderD3D9(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DPIXELSHADER9 *pShader, std::string *errorMessage) { + LPD3DBLOB pShaderCode = CompileShaderToByteCodeD3D9(code, "ps_2_0", errorMessage); + if (pShaderCode) { + // Create pixel shader. + device->CreatePixelShader((DWORD*)pShaderCode->GetBufferPointer(), pShader); + pShaderCode->Release(); + return true; } else { - errorMessage = ""; - } - - if (FAILED(hr) || !pShaderCode) { - if (pShaderCode) - pShaderCode->Release(); return false; } - - // Create pixel shader. - device->CreateVertexShader( (DWORD*)pShaderCode->GetBufferPointer(), - pShader ); - - pShaderCode->Release(); - - return true; } -} // namespace +bool CompileVertexShaderD3D9(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DVERTEXSHADER9 *pShader, std::string *errorMessage) { + LPD3DBLOB pShaderCode = CompileShaderToByteCodeD3D9(code, "vs_2_0", errorMessage); + if (pShaderCode) { + // Create vertex shader. + device->CreateVertexShader((DWORD*)pShaderCode->GetBufferPointer(), pShader); + pShaderCode->Release(); + return true; + } else { + return false; + } +} #endif diff --git a/Common/GPU/D3D9/D3D9ShaderCompiler.h b/Common/GPU/D3D9/D3D9ShaderCompiler.h index 6c4aaf75a2..1de2a158d0 100644 --- a/Common/GPU/D3D9/D3D9ShaderCompiler.h +++ b/Common/GPU/D3D9/D3D9ShaderCompiler.h @@ -1,6 +1,7 @@ #pragma once #include "Common/CommonWindows.h" +#include "Common/GPU/D3D9/D3DCompilerLoader.h" #include #include @@ -8,9 +9,7 @@ struct ID3DXConstantTable; -namespace DX9 { +LPD3DBLOB CompileShaderToByteCodeD3D9(const char *code, const char *target, std::string *errorMessage); -bool CompilePixelShader(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DPIXELSHADER9 *pShader, ID3DXConstantTable **pShaderTable, std::string &errorMessage); -bool CompileVertexShader(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DVERTEXSHADER9 *pShader, ID3DXConstantTable **pShaderTable, std::string &errorMessage); - -} // namespace DX9 +bool CompilePixelShaderD3D9(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DPIXELSHADER9 *pShader, std::string *errorMessage); +bool CompileVertexShaderD3D9(LPDIRECT3DDEVICE9 device, const char *code, LPDIRECT3DVERTEXSHADER9 *pShader, std::string *errorMessage); diff --git a/Common/GPU/D3D9/D3D9StateCache.cpp b/Common/GPU/D3D9/D3D9StateCache.cpp index ed0bbdeff4..a799713c95 100644 --- a/Common/GPU/D3D9/D3D9StateCache.cpp +++ b/Common/GPU/D3D9/D3D9StateCache.cpp @@ -5,7 +5,6 @@ namespace DX9 { DirectXState dxstate; -GLExtensions gl_extensions; LPDIRECT3DDEVICE9 pD3Ddevice = nullptr; LPDIRECT3DDEVICE9EX pD3DdeviceEx = nullptr; @@ -64,14 +63,6 @@ void DirectXState::Restore() { texAddressV.restore(); count++; } -void CheckGLExtensions() { - static bool done = false; - if (done) - return; - done = true; - memset(&gl_extensions, 0, sizeof(gl_extensions)); -} - } // namespace DX9 #endif // _MSC_VER diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 6c7b5c233f..44a559ee50 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -254,7 +254,7 @@ public: } bool Compile(LPDIRECT3DDEVICE9 device, const uint8_t *data, size_t size); void Apply(LPDIRECT3DDEVICE9 device) { - if (stage_ == ShaderStage::FRAGMENT) { + if (stage_ == ShaderStage::Fragment) { device->SetPixelShader(pshader_); } else { device->SetVertexShader(vshader_); @@ -670,6 +670,8 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID HRESULT fboINTZ = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ); supportsINTZ = SUCCEEDED(displayINTZ) && SUCCEEDED(fboINTZ); } + + shaderLanguageDesc_.Init(HLSL_D3D9); } D3D9Context::~D3D9Context() { @@ -697,10 +699,10 @@ Pipeline *D3D9Context::CreateGraphicsPipeline(const PipelineDesc &desc) { delete pipeline; return NULL; } - if (iter->GetStage() == ShaderStage::FRAGMENT) { + if (iter->GetStage() == ShaderStage::Fragment) { pipeline->pshader = static_cast(iter); } - else if (iter->GetStage() == ShaderStage::VERTEX) { + else if (iter->GetStage() == ShaderStage::Vertex) { pipeline->vshader = static_cast(iter); } } @@ -1034,10 +1036,10 @@ bool D3D9ShaderModule::Compile(LPDIRECT3DDEVICE9 device, const uint8_t *data, si auto compile = [&](const char *profile) -> HRESULT { return dyn_D3DCompile(source, (UINT)strlen(source), nullptr, defines, includes, "main", profile, 0, 0, &codeBuffer, &errorBuffer); }; - HRESULT hr = compile(stage_ == ShaderStage::FRAGMENT ? "ps_2_0" : "vs_2_0"); + HRESULT hr = compile(stage_ == ShaderStage::Fragment ? "ps_2_0" : "vs_2_0"); if (FAILED(hr) && hr == D3DXERR_INVALIDDATA) { // Might be a post shader. Let's try using shader model 3. - hr = compile(stage_ == ShaderStage::FRAGMENT ? "ps_3_0" : "vs_3_0"); + hr = compile(stage_ == ShaderStage::Fragment ? "ps_3_0" : "vs_3_0"); } if (FAILED(hr)) { const char *error = errorBuffer ? (const char *)errorBuffer->GetBufferPointer() : "(no errorbuffer returned)"; @@ -1055,7 +1057,7 @@ bool D3D9ShaderModule::Compile(LPDIRECT3DDEVICE9 device, const uint8_t *data, si } bool success = false; - if (stage_ == ShaderStage::FRAGMENT) { + if (stage_ == ShaderStage::Fragment) { HRESULT result = device->CreatePixelShader((DWORD *)codeBuffer->GetBufferPointer(), &pshader_); success = SUCCEEDED(result); } else { @@ -1072,28 +1074,26 @@ bool D3D9ShaderModule::Compile(LPDIRECT3DDEVICE9 device, const uint8_t *data, si class D3D9Framebuffer : public Framebuffer { public: + D3D9Framebuffer(int width, int height) { + width_ = width; + height_ = height; + } ~D3D9Framebuffer(); - uint32_t id; - LPDIRECT3DSURFACE9 surf; - LPDIRECT3DSURFACE9 depthstencil; - LPDIRECT3DTEXTURE9 tex; - LPDIRECT3DTEXTURE9 depthstenciltex; - int width; - int height; - FBColorDepth colorDepth; + uint32_t id = 0; + LPDIRECT3DSURFACE9 surf = nullptr; + LPDIRECT3DSURFACE9 depthstencil = nullptr; + LPDIRECT3DTEXTURE9 tex = nullptr; + LPDIRECT3DTEXTURE9 depthstenciltex = nullptr; }; Framebuffer *D3D9Context::CreateFramebuffer(const FramebufferDesc &desc) { static uint32_t id = 0; - D3D9Framebuffer *fbo = new D3D9Framebuffer{}; - fbo->width = desc.width; - fbo->height = desc.height; - fbo->colorDepth = desc.colorDepth; + D3D9Framebuffer *fbo = new D3D9Framebuffer(desc.width, desc.height); fbo->depthstenciltex = nullptr; - HRESULT rtResult = device_->CreateTexture(fbo->width, fbo->height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &fbo->tex, NULL); + HRESULT rtResult = device_->CreateTexture(desc.width, desc.height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &fbo->tex, NULL); if (FAILED(rtResult)) { ERROR_LOG(G3D, "Failed to create render target"); delete fbo; @@ -1103,12 +1103,12 @@ Framebuffer *D3D9Context::CreateFramebuffer(const FramebufferDesc &desc) { HRESULT dsResult; if (supportsINTZ) { - dsResult = device_->CreateTexture(fbo->width, fbo->height, 1, D3DUSAGE_DEPTHSTENCIL, FOURCC_INTZ, D3DPOOL_DEFAULT, &fbo->depthstenciltex, NULL); + dsResult = device_->CreateTexture(desc.width, desc.height, 1, D3DUSAGE_DEPTHSTENCIL, FOURCC_INTZ, D3DPOOL_DEFAULT, &fbo->depthstenciltex, NULL); if (SUCCEEDED(dsResult)) { dsResult = fbo->depthstenciltex->GetSurfaceLevel(0, &fbo->depthstencil); } } else { - dsResult = device_->CreateDepthStencilSurface(fbo->width, fbo->height, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, FALSE, &fbo->depthstencil, NULL); + dsResult = device_->CreateDepthStencilSurface(desc.width, desc.height, D3DFMT_D24S8, D3DMULTISAMPLE_NONE, 0, FALSE, &fbo->depthstencil, NULL); } if (FAILED(dsResult)) { ERROR_LOG(G3D, "Failed to create depth buffer"); @@ -1210,8 +1210,8 @@ void D3D9Context::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChan void D3D9Context::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { D3D9Framebuffer *fb = (D3D9Framebuffer *)fbo; if (fb) { - *w = fb->width; - *h = fb->height; + *w = fb->Width(); + *h = fb->Height(); } else { *w = targetWidth_; *h = targetHeight_; diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index 7048b6198a..a5d12aae07 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -77,19 +77,28 @@ bool GLExtensions::VersionGEThan(int major, int minor, int sub) { } int GLExtensions::GLSLVersion() { - // Used for shader translation and core contexts (Apple drives fail without an exact match.) - if (gl_extensions.VersionGEThan(3, 3)) { - return gl_extensions.ver[0] * 100 + gl_extensions.ver[1] * 10; - } else if (gl_extensions.VersionGEThan(3, 2)) { - return 150; - } else if (gl_extensions.VersionGEThan(3, 1)) { - return 140; - } else if (gl_extensions.VersionGEThan(3, 0)) { - return 130; - } else if (gl_extensions.VersionGEThan(2, 1)) { - return 120; + if (gl_extensions.IsGLES) { + if (gl_extensions.GLES3) { + // GLSL version matches ES version. + return gl_extensions.ver[0] * 100 + gl_extensions.ver[1] * 10; + } else { + return 100; + } } else { - return 110; + // Used for shader translation and core contexts (Apple drives fail without an exact match.) + if (gl_extensions.VersionGEThan(3, 3)) { + return gl_extensions.ver[0] * 100 + gl_extensions.ver[1] * 10; + } else if (gl_extensions.VersionGEThan(3, 2)) { + return 150; + } else if (gl_extensions.VersionGEThan(3, 1)) { + return 140; + } else if (gl_extensions.VersionGEThan(3, 0)) { + return 130; + } else if (gl_extensions.VersionGEThan(2, 1)) { + return 120; + } else { + return 110; + } } } @@ -160,6 +169,7 @@ void CheckGLExtensions() { gl_extensions.gpuVendor = GPU_VENDOR_IMGTEC; } else if (vendor == "Qualcomm") { gl_extensions.gpuVendor = GPU_VENDOR_QUALCOMM; + sscanf(renderer, "Adreno (TM) %d", &gl_extensions.modelNumber); } else if (vendor == "Broadcom") { gl_extensions.gpuVendor = GPU_VENDOR_BROADCOM; // Just for reference: Galaxy Y has renderer == "VideoCore IV HW" @@ -354,6 +364,9 @@ void CheckGLExtensions() { gl_extensions.EXT_draw_instanced = g_set_gl_extensions.count("GL_EXT_draw_instanced") != 0; gl_extensions.ARB_draw_instanced = g_set_gl_extensions.count("GL_ARB_draw_instanced") != 0; gl_extensions.ARB_cull_distance = g_set_gl_extensions.count("GL_ARB_cull_distance") != 0; + gl_extensions.ARB_depth_clamp = g_set_gl_extensions.count("GL_ARB_depth_clamp") != 0; + gl_extensions.ARB_uniform_buffer_object = g_set_gl_extensions.count("GL_ARB_uniform_buffer_object") != 0; + gl_extensions.ARB_explicit_attrib_location = g_set_gl_extensions.count("GL_ARB_explicit_attrib_location") != 0; if (gl_extensions.IsGLES) { gl_extensions.OES_texture_npot = g_set_gl_extensions.count("GL_OES_texture_npot") != 0; @@ -364,7 +377,6 @@ void CheckGLExtensions() { gl_extensions.EXT_blend_minmax = g_set_gl_extensions.count("GL_EXT_blend_minmax") != 0; gl_extensions.EXT_unpack_subimage = g_set_gl_extensions.count("GL_EXT_unpack_subimage") != 0; gl_extensions.EXT_shader_framebuffer_fetch = g_set_gl_extensions.count("GL_EXT_shader_framebuffer_fetch") != 0; - gl_extensions.NV_shader_framebuffer_fetch = g_set_gl_extensions.count("GL_NV_shader_framebuffer_fetch") != 0; gl_extensions.ARM_shader_framebuffer_fetch = g_set_gl_extensions.count("GL_ARM_shader_framebuffer_fetch") != 0; gl_extensions.OES_texture_float = g_set_gl_extensions.count("GL_OES_texture_float") != 0; gl_extensions.EXT_buffer_storage = g_set_gl_extensions.count("GL_EXT_buffer_storage") != 0; @@ -471,9 +483,11 @@ void CheckGLExtensions() { } } - // Now, Adreno lies. So let's override it. - if (gl_extensions.gpuVendor == GPU_VENDOR_QUALCOMM) { - WARN_LOG(G3D, "Detected Adreno - lowering int precision"); + // Now, old Adreno lies about supporting full precision integers. So let's override it. + // The model number comparison should probably be 400 or 500. This causes us to avoid depal-in-shader. + // It seems though that this caused large perf regressions on Adreno 5xx, so I've bumped it up to 600. + if (gl_extensions.gpuVendor == GPU_VENDOR_QUALCOMM && gl_extensions.modelNumber < 600) { + WARN_LOG(G3D, "Detected old Adreno - lowering reported int precision for safety"); gl_extensions.range[1][5][0] = 15; gl_extensions.range[1][5][1] = 15; } @@ -498,14 +512,14 @@ void CheckGLExtensions() { } if (gl_extensions.VersionGEThan(3, 1)) { gl_extensions.ARB_draw_instanced = true; - // ARB_uniform_buffer_object = true; + gl_extensions.ARB_uniform_buffer_object = true; } if (gl_extensions.VersionGEThan(3, 2)) { - // ARB_depth_clamp = true; + gl_extensions.ARB_depth_clamp = true; } if (gl_extensions.VersionGEThan(3, 3)) { gl_extensions.ARB_blend_func_extended = true; - // ARB_explicit_attrib_location = true; + gl_extensions.ARB_explicit_attrib_location = true; } if (gl_extensions.VersionGEThan(4, 0)) { // ARB_gpu_shader5 = true; @@ -538,13 +552,6 @@ void CheckGLExtensions() { } } -#ifdef __APPLE__ - if (!gl_extensions.IsGLES && !gl_extensions.IsCoreContext) { - // Apple doesn't allow OpenGL 3.x+ in compatibility contexts. - gl_extensions.ForceGL2 = true; - } -#endif - ProcessGPUFeatures(); int error = glGetError(); diff --git a/Common/GPU/OpenGL/GLFeatures.h b/Common/GPU/OpenGL/GLFeatures.h index 0c4a619b45..668de606d5 100644 --- a/Common/GPU/OpenGL/GLFeatures.h +++ b/Common/GPU/OpenGL/GLFeatures.h @@ -34,11 +34,11 @@ struct GLExtensions { int ver[3]; int gpuVendor; char model[128]; + int modelNumber; bool IsGLES; bool IsCoreContext; bool GLES3; // true if the full OpenGL ES 3.0 is supported - bool ForceGL2; // OES bool OES_depth24; @@ -55,6 +55,7 @@ struct GLExtensions { bool ARB_pixel_buffer_object; bool ARB_blend_func_extended; // dual source blending bool EXT_blend_func_extended; // dual source blending (GLES, new 2015) + bool ARB_explicit_attrib_location; bool ARB_shader_image_load_store; bool ARB_shading_language_420pack; bool ARB_conservative_depth; @@ -64,6 +65,8 @@ struct GLExtensions { bool ARB_draw_instanced; bool ARB_buffer_storage; bool ARB_cull_distance; + bool ARB_depth_clamp; + bool ARB_uniform_buffer_object; // EXT bool EXT_swap_control_tear; @@ -82,7 +85,6 @@ struct GLExtensions { bool EXT_clip_cull_distance; // NV - bool NV_shader_framebuffer_fetch; bool NV_copy_image; bool NV_framebuffer_blit; bool NV_pixel_buffer_object; // GL_NV_pixel_buffer_object diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 7776d7da4d..d81688189a 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -277,21 +277,30 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski glCompileShader(shader); GLint success = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + std::string infoLog = GetInfoLog(shader, glGetShaderiv, glGetShaderInfoLog); if (!success) { - std::string infoLog = GetInfoLog(shader, glGetShaderiv, glGetShaderInfoLog); -#ifdef __ANDROID__ - ERROR_LOG(G3D, "Error in shader compilation! %s\n", infoLog.c_str()); - ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); -#endif - ERROR_LOG(G3D, "Error in shader compilation for: %s", step.create_shader.shader->desc.c_str()); - ERROR_LOG(G3D, "Info log: %s", infoLog.c_str()); - ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code); + std::string errorString = StringFromFormat( + "Error in shader compilation for: %s\n" + "Info log: %s\n" + "Shader source:\n%s\n//END\n\n", + step.create_shader.shader->desc.c_str(), + infoLog.c_str(), + LineNumberString(code).c_str()); + std::vector lines; + SplitString(errorString, '\n', lines); + for (auto &line : lines) { + ERROR_LOG(G3D, "%s", line.c_str()); + } + if (errorCallback_) { + std::string desc = StringFromFormat("Shader compilation failed: %s", step.create_shader.stage == GL_VERTEX_SHADER ? "vertex" : "fragment"); + errorCallback_(desc.c_str(), errorString.c_str(), errorCallbackUserData_); + } Reporting::ReportMessage("Error in shader compilation: info: %s\n%s\n%s", infoLog.c_str(), step.create_shader.shader->desc.c_str(), (const char *)code); #ifdef SHADERLOG OutputDebugStringUTF8(infoLog.c_str()); #endif step.create_shader.shader->failed = true; - step.create_shader.shader->error = infoLog; + step.create_shader.shader->error = infoLog; // Hm, we never use this. } // Before we throw away the code, attach it to the shader for debugging. step.create_shader.shader->code = code; @@ -376,17 +385,21 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski CHECK_GL_ERROR_IF_DEBUG(); // TODO: Use GL_KHR_no_error or a debug callback, where supported? - if (allocatedTextures) { + if (false && allocatedTextures) { // Users may use replacements or scaling, with high render resolutions, and run out of VRAM. // This detects that, rather than looking like PPSSPP is broken. - // Calling glGetError() isn't great, but at the end of init shouldn't be too bad... + // Calling glGetError() isn't great, but at the end of init, only after creating textures, shouldn't be too bad... GLenum err = glGetError(); if (err == GL_OUT_OF_MEMORY) { WARN_LOG_REPORT(G3D, "GL ran out of GPU memory; switching to low memory mode"); sawOutOfMemory_ = true; } else if (err != GL_NO_ERROR) { // We checked the err anyway, might as well log if there is one. - WARN_LOG(G3D, "Got an error after init: %08x (%s)", err, GLEnumToString(err).c_str()); + std::string errorString = GLEnumToString(err); + WARN_LOG(G3D, "Got an error after init: %08x (%s)", err, errorString.c_str()); + if (errorCallback_) { + errorCallback_("GL frame init error", errorString.c_str(), errorCallbackUserData_); + } } } @@ -425,7 +438,7 @@ void GLQueueRunner::InitCreateFramebuffer(const GLRInitStep &step) { tex.wrapT = GL_CLAMP_TO_EDGE; tex.magFilter = linear ? GL_LINEAR : GL_NEAREST; tex.minFilter = linear ? GL_LINEAR : GL_NEAREST; - tex.canWrap = isPowerOf2(fbo->width) && isPowerOf2(fbo->height); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex.wrapS); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex.wrapT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex.magFilter); @@ -617,6 +630,7 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal }; int invalidateAllMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + /* for (int j = 0; j < (int)steps.size() - 1; ++j) { GLRStep &primaryStep = *steps[j]; if (primaryStep.stepType == GLRStepType::RENDER) { @@ -648,7 +662,7 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal primaryStep.commands.push_back(data); } } - } + }*/ CHECK_GL_ERROR_IF_DEBUG(); size_t renderCount = 0; @@ -982,6 +996,32 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last CHECK_GL_ERROR_IF_DEBUG(); break; } + case GLRRenderCommand::UNIFORM4UI: + { + _dbg_assert_(curProgram); + int loc = c.uniform4.loc ? *c.uniform4.loc : -1; + if (c.uniform4.name) { + loc = curProgram->GetUniformLoc(c.uniform4.name); + } + if (loc >= 0) { + switch (c.uniform4.count) { + case 1: + glUniform1uiv(loc, 1, (GLuint *)&c.uniform4.v[0]); + break; + case 2: + glUniform2uiv(loc, 1, (GLuint *)c.uniform4.v); + break; + case 3: + glUniform3uiv(loc, 1, (GLuint *)c.uniform4.v); + break; + case 4: + glUniform4uiv(loc, 1, (GLuint *)c.uniform4.v); + break; + } + } + CHECK_GL_ERROR_IF_DEBUG(); + break; + } case GLRRenderCommand::UNIFORM4I: { _dbg_assert_(curProgram); @@ -1423,7 +1463,6 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { if (convert && tempBuffer_ && readbackBuffer_) { ConvertFromRGBA8888(readbackBuffer_, tempBuffer_, pixelStride, pixelStride, rect.w, rect.h, pass.readback.dstFormat); } - CHECK_GL_ERROR_IF_DEBUG(); } diff --git a/Common/GPU/OpenGL/GLQueueRunner.h b/Common/GPU/OpenGL/GLQueueRunner.h index 742f885768..07837421be 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.h +++ b/Common/GPU/OpenGL/GLQueueRunner.h @@ -6,6 +6,7 @@ #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/DataFormat.h" +#include "Common/GPU/Shader.h" #include "Common/Data/Collections/TinySet.h" struct GLRViewport { @@ -41,6 +42,7 @@ enum class GLRRenderCommand : uint8_t { BLENDCOLOR, LOGICOP, UNIFORM4I, + UNIFORM4UI, UNIFORM4F, UNIFORMMATRIX, TEXTURESAMPLER, @@ -136,7 +138,7 @@ struct GLRRenderData { int16_t scissorY; int16_t scissorW; int16_t scissorH; - } clear; + } clear; // also used for invalidate struct { int slot; GLRTexture *texture; @@ -341,6 +343,11 @@ class GLQueueRunner { public: GLQueueRunner() {} + void SetErrorCallback(ErrorCallbackFn callback, void *userdata) { + errorCallback_ = callback; + errorCallbackUserData_ = userdata; + } + void RunInitSteps(const std::vector &steps, bool skipGLCalls); void RunSteps(const std::vector &steps, bool skipGLCalls); @@ -423,4 +430,7 @@ private: bool sawOutOfMemory_ = false; bool useDebugGroups_ = false; + + ErrorCallbackFn errorCallback_ = nullptr; + void *errorCallbackUserData_ = nullptr; }; diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index 7764adfde8..97f5bcb0a6 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -5,6 +5,7 @@ #include "Common/Log.h" #include "Common/MemoryUtil.h" +#include "Common/Math/math_util.h" #if 0 // def _DEBUG #define VLOG(...) INFO_LOG(G3D, __VA_ARGS__) @@ -19,6 +20,23 @@ static bool OnRenderThread() { } #endif +GLRTexture::GLRTexture(int width, int height, int numMips) { + if (gl_extensions.OES_texture_npot) { + canWrap = true; + } else { + canWrap = isPowerOf2(width) && isPowerOf2(height); + } + w = width; + h = height; + this->numMips = numMips; +} + +GLRTexture::~GLRTexture() { + if (texture) { + glDeleteTextures(1, &texture); + } +} + void GLDeleter::Take(GLDeleter &other) { _assert_msg_(IsEmpty(), "Deleter already has stuff"); shaders = std::move(other.shaders); diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index a2f3a7effe..fc774aa1c9 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -23,13 +23,12 @@ class DrawContext; class GLRTexture { public: - ~GLRTexture() { - if (texture) { - glDeleteTextures(1, &texture); - } - } + GLRTexture(int width, int height, int numMips); + ~GLRTexture(); GLuint texture = 0; + uint16_t w; + uint16_t h; // We don't trust OpenGL defaults - setting wildly off values ensures that we'll end up overwriting these parameters. GLenum target = 0xFFFF; @@ -37,6 +36,7 @@ public: GLenum wrapT = 0xFFFF; GLenum magFilter = 0xFFFF; GLenum minFilter = 0xFFFF; + uint8_t numMips = 0; bool canWrap = true; float anisotropy = -100000.0f; float minLod = -1000.0f; @@ -47,7 +47,8 @@ public: class GLRFramebuffer { public: GLRFramebuffer(int _width, int _height, bool z_stencil) - : width(_width), height(_height), z_stencil_(z_stencil) { + : width(_width), height(_height), z_stencil_(z_stencil), + color_texture(_width, _height, 1), z_stencil_texture(_width, _height, 1) { } ~GLRFramebuffer(); @@ -357,6 +358,10 @@ public: GLRenderManager(); ~GLRenderManager(); + void SetErrorCallback(ErrorCallbackFn callback, void *userdata) { + queueRunner_.SetErrorCallback(callback, userdata); + } + void ThreadStart(Draw::DrawContext *draw); void ThreadEnd(); bool ThreadFrame(); // Returns false to request exiting the loop. @@ -374,9 +379,11 @@ public: void WaitUntilQueueIdle(); // Creation commands. These were not needed in Vulkan since there we can do that on the main thread. - GLRTexture *CreateTexture(GLenum target) { + // We pass in width/height here even though it's not strictly needed until we support glTextureStorage + // and then we'll also need formats and stuff. + GLRTexture *CreateTexture(GLenum target, int width, int height, int numMips) { GLRInitStep step{ GLRInitStepType::CREATE_TEXTURE }; - step.create_texture.texture = new GLRTexture(); + step.create_texture.texture = new GLRTexture(width, height, numMips); step.create_texture.texture->target = target; initSteps_.push_back(step); return step.create_texture.texture; @@ -655,6 +662,30 @@ public: curRenderStep_->commands.push_back(data); } + void SetUniformUI(const GLint *loc, int count, const uint32_t *udata) { + _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); +#ifdef _DEBUG + _dbg_assert_(curProgram_); +#endif + GLRRenderData data{ GLRRenderCommand::UNIFORM4UI }; + data.uniform4.loc = loc; + data.uniform4.count = count; + memcpy(data.uniform4.v, udata, sizeof(uint32_t) * count); + curRenderStep_->commands.push_back(data); + } + + void SetUniformUI1(const GLint *loc, uint32_t udata) { + _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); +#ifdef _DEBUG + _dbg_assert_(curProgram_); +#endif + GLRRenderData data{ GLRRenderCommand::UNIFORM4UI }; + data.uniform4.loc = loc; + data.uniform4.count = 1; + memcpy(data.uniform4.v, &udata, sizeof(udata)); + curRenderStep_->commands.push_back(data); + } + void SetUniformF(const GLint *loc, int count, const float *udata) { _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER); #ifdef _DEBUG diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index a58432e14b..05b27923c2 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -8,6 +8,7 @@ #include "Common/Math/math_util.h" #include "Common/Math/lin/matrix4x4.h" #include "Common/GPU/thin3d.h" +#include "Common/GPU/Shader.h" #include "Common/GPU/OpenGL/DataFormatGL.h" #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLDebugLog.h" @@ -133,25 +134,6 @@ static const unsigned short primToGL[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, -#if !defined(USING_GLES2) // TODO: Remove when we have better headers - GL_PATCHES, - GL_LINES_ADJACENCY, - GL_LINE_STRIP_ADJACENCY, - GL_TRIANGLES_ADJACENCY, - GL_TRIANGLE_STRIP_ADJACENCY, -#elif !defined(IOS) - GL_POINTS, - GL_POINTS, - GL_POINTS, - GL_POINTS, - GL_POINTS, -#else - GL_POINTS, - GL_POINTS, - GL_POINTS, - GL_POINTS, - GL_POINTS, -#endif }; class OpenGLBuffer; @@ -213,14 +195,12 @@ public: GLuint ShaderStageToOpenGL(ShaderStage stage) { switch (stage) { - case ShaderStage::VERTEX: return GL_VERTEX_SHADER; + case ShaderStage::Vertex: return GL_VERTEX_SHADER; #ifndef USING_GLES2 - case ShaderStage::COMPUTE: return GL_COMPUTE_SHADER; - case ShaderStage::EVALUATION: return GL_TESS_EVALUATION_SHADER; - case ShaderStage::CONTROL: return GL_TESS_CONTROL_SHADER; - case ShaderStage::GEOMETRY: return GL_GEOMETRY_SHADER; + case ShaderStage::Compute: return GL_COMPUTE_SHADER; + case ShaderStage::Geometry: return GL_GEOMETRY_SHADER; #endif - case ShaderStage::FRAGMENT: + case ShaderStage::Fragment: default: return GL_FRAGMENT_SHADER; } @@ -254,7 +234,7 @@ public: private: GLRenderManager *render_; ShaderStage stage_; - ShaderLanguage language_ = ShaderLanguage::GLSL_ES_200; + ShaderLanguage language_ = GLSL_1xx; GLRShader *shader_ = nullptr; GLuint glstage_ = 0; std::string source_; // So we can recompile in case of context loss. @@ -308,7 +288,7 @@ public: bool LinkShaders(); bool RequiresBuffer() override { - return inputLayout->RequiresBuffer(); + return inputLayout && inputLayout->RequiresBuffer(); } GLuint prim = 0; @@ -345,13 +325,19 @@ public: return caps_; } uint32_t GetSupportedShaderLanguages() const override { - if (gl_extensions.IsGLES) - return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_ES_300; - else - return (uint32_t)ShaderLanguage::GLSL_ES_200 | (uint32_t)ShaderLanguage::GLSL_410; + if (gl_extensions.GLES3) { + return (uint32_t)(ShaderLanguage::GLSL_3xx | ShaderLanguage::GLSL_1xx); + } else { + return (uint32_t)ShaderLanguage::GLSL_1xx; + } } + uint32_t GetDataFormatSupport(DataFormat fmt) const override; + void SetErrorCallback(ErrorCallbackFn callback, void *userdata) override { + renderManager_.SetErrorCallback(callback, userdata); + } + DepthStencilState *CreateDepthStencilState(const DepthStencilStateDesc &desc) override; BlendState *CreateBlendState(const BlendStateDesc &desc) override; SamplerState *CreateSamplerState(const SamplerStateDesc &desc) override; @@ -381,7 +367,7 @@ public: void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void BindSamplerStates(int start, int count, SamplerState **states) override { - if (start + count >= MAX_TEXTURE_SLOTS) { + if (start + count > MAX_TEXTURE_SLOTS) { return; } for (int i = 0; i < count; i++) { @@ -492,7 +478,9 @@ private: // Bound state OpenGLSamplerState *boundSamplers_[MAX_TEXTURE_SLOTS]{}; - OpenGLTexture *boundTextures_[MAX_TEXTURE_SLOTS]{}; + // Point to GLRTexture directly because they can point to the textures + // in framebuffers too (which also can be bound). + const GLRTexture *boundTextures_[MAX_TEXTURE_SLOTS]{}; OpenGLPipeline *curPipeline_ = nullptr; OpenGLBuffer *curVBuffers_[4]{}; @@ -597,6 +585,76 @@ OpenGLContext::OpenGLContext() { // TODO: Should this workaround be removed for newer devices/drivers? bugs_.Infest(Bugs::PVR_GENMIPMAP_HEIGHT_GREATER); } + + shaderLanguageDesc_.Init(GLSL_1xx); + + shaderLanguageDesc_.glslVersionNumber = gl_extensions.GLSLVersion(); + + snprintf(shaderLanguageDesc_.driverInfo, sizeof(shaderLanguageDesc_.driverInfo), + "%s - GLSL %d", gl_extensions.model, gl_extensions.GLSLVersion()); + // Detect shader language features. + if (gl_extensions.IsGLES) { + shaderLanguageDesc_.gles = true; + if (gl_extensions.GLES3) { + shaderLanguageDesc_.shaderLanguage = ShaderLanguage::GLSL_3xx; + shaderLanguageDesc_.fragColor0 = "fragColor0"; + shaderLanguageDesc_.texture = "texture"; + shaderLanguageDesc_.glslES30 = true; + shaderLanguageDesc_.bitwiseOps = true; + shaderLanguageDesc_.texelFetch = "texelFetch"; + shaderLanguageDesc_.varying_vs = "out"; + shaderLanguageDesc_.varying_fs = "in"; + shaderLanguageDesc_.attribute = "in"; + } else { + shaderLanguageDesc_.shaderLanguage = ShaderLanguage::GLSL_1xx; + if (gl_extensions.EXT_gpu_shader4) { + shaderLanguageDesc_.bitwiseOps = true; + shaderLanguageDesc_.texelFetch = "texelFetch2D"; + } + if (gl_extensions.EXT_blend_func_extended) { + // Oldy moldy GLES, so use the fixed output name. + shaderLanguageDesc_.fragColor1 = "gl_SecondaryFragColorEXT"; + } + } + } else { + if (gl_extensions.IsCoreContext) { + if (gl_extensions.VersionGEThan(3, 3, 0)) { + shaderLanguageDesc_.shaderLanguage = ShaderLanguage::GLSL_3xx; + shaderLanguageDesc_.fragColor0 = "fragColor0"; + shaderLanguageDesc_.texture = "texture"; + shaderLanguageDesc_.glslES30 = true; + shaderLanguageDesc_.bitwiseOps = true; + shaderLanguageDesc_.texelFetch = "texelFetch"; + shaderLanguageDesc_.varying_vs = "out"; + shaderLanguageDesc_.varying_fs = "in"; + shaderLanguageDesc_.attribute = "in"; + } else if (gl_extensions.VersionGEThan(3, 0, 0)) { + // Hm, I think this is wrong. This should be outside "if (gl_extensions.IsCoreContext)". + shaderLanguageDesc_.shaderLanguage = ShaderLanguage::GLSL_1xx; + shaderLanguageDesc_.fragColor0 = "fragColor0"; + shaderLanguageDesc_.bitwiseOps = true; + shaderLanguageDesc_.texelFetch = "texelFetch"; + } else { + // This too... + shaderLanguageDesc_.shaderLanguage = ShaderLanguage::GLSL_1xx; + if (gl_extensions.EXT_gpu_shader4) { + shaderLanguageDesc_.bitwiseOps = true; + shaderLanguageDesc_.texelFetch = "texelFetch2D"; + } + } + } + } + + if (gl_extensions.IsGLES) { + caps_.framebufferFetchSupported = (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch); + if (gl_extensions.EXT_shader_framebuffer_fetch) { + shaderLanguageDesc_.framebufferFetchExtension = "#extension GL_EXT_shader_framebuffer_fetch : require"; + shaderLanguageDesc_.lastFragData = gl_extensions.GLES3 ? "fragColor0" : "gl_LastFragData[0]"; + } else if (gl_extensions.ARM_shader_framebuffer_fetch) { + shaderLanguageDesc_.framebufferFetchExtension = "#extension GL_ARM_shader_framebuffer_fetch : require"; + shaderLanguageDesc_.lastFragData = "gl_LastFragColorARM"; + } + } } OpenGLContext::~OpenGLContext() { @@ -663,9 +721,7 @@ public: bool HasMips() const { return mipLevels_ > 1 || generatedMips_; } - bool CanWrap() const { - return canWrap_; - } + TextureType GetType() const { return type_; } void Bind(int stage) { render_->BindTexture(stage, tex_); @@ -673,6 +729,9 @@ public: int NumMipmaps() const { return mipLevels_; } + const GLRTexture *GetTex() const { + return tex_; + } private: void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback); @@ -684,7 +743,6 @@ private: TextureType type_; int mipLevels_; bool generatedMips_; - bool canWrap_; }; OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) { @@ -695,9 +753,8 @@ OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : format_ = desc.format; type_ = desc.type; GLenum target = TypeToTarget(desc.type); - tex_ = render->CreateTexture(target); + tex_ = render->CreateTexture(target, desc.width, desc.height, desc.mipLevels); - canWrap_ = isPowerOf2(width_) && isPowerOf2(height_); mipLevels_ = desc.mipLevels; if (desc.initData.empty()) return; @@ -731,15 +788,16 @@ OpenGLTexture::~OpenGLTexture() { class OpenGLFramebuffer : public Framebuffer { public: - OpenGLFramebuffer(GLRenderManager *render) : render_(render) {} + OpenGLFramebuffer(GLRenderManager *render, GLRFramebuffer *framebuffer) : render_(render), framebuffer_(framebuffer) { + width_ = framebuffer->width; + height_ = framebuffer->height; + } ~OpenGLFramebuffer() { - render_->DeleteFramebuffer(framebuffer); + render_->DeleteFramebuffer(framebuffer_); } GLRenderManager *render_; - GLRFramebuffer *framebuffer = nullptr; - - FBColorDepth colorDepth = FBO_8888; + GLRFramebuffer *framebuffer_ = nullptr; }; // TODO: SSE/NEON optimize, and move to ColorConv.cpp. @@ -841,7 +899,7 @@ bool OpenGLContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBit aspect |= GL_DEPTH_BUFFER_BIT; if (channelBits & FB_STENCIL_BIT) aspect |= GL_STENCIL_BUFFER_BIT; - renderManager_.CopyFramebufferToMemorySync(fb ? fb->framebuffer : nullptr, aspect, x, y, w, h, dataFormat, (uint8_t *)pixels, pixelStride, tag); + renderManager_.CopyFramebufferToMemorySync(fb ? fb->framebuffer_ : nullptr, aspect, x, y, w, h, dataFormat, (uint8_t *)pixels, pixelStride, tag); return true; } @@ -972,7 +1030,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { ERROR_LOG(G3D, "Invalid primitive type"); return nullptr; } - if (!desc.depthStencil || !desc.blend || !desc.raster || !desc.inputLayout) { + if (!desc.depthStencil || !desc.blend || !desc.raster) { ERROR_LOG(G3D, "Incomplete prim desciption"); return nullptr; } @@ -1002,7 +1060,9 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { pipeline->depthStencil->AddRef(); pipeline->blend->AddRef(); pipeline->raster->AddRef(); - pipeline->inputLayout->AddRef(); + if (pipeline->inputLayout) { + pipeline->inputLayout->AddRef(); + } return pipeline; } else { ERROR_LOG(G3D, "Failed to create pipeline - shaders failed to link"); @@ -1012,7 +1072,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { } void OpenGLContext::BindTextures(int start, int count, Texture **textures) { - if (start + count >= MAX_TEXTURE_SLOTS) { + if (start + count > MAX_TEXTURE_SLOTS) { return; } for (int i = start; i < start + count; i++) { @@ -1023,14 +1083,14 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) { continue; } glTex->Bind(i); - boundTextures_[i] = glTex; + boundTextures_[i] = glTex->GetTex(); } } void OpenGLContext::ApplySamplers() { for (int i = 0; i < MAX_TEXTURE_SLOTS; i++) { const OpenGLSamplerState *samp = boundSamplers_[i]; - const OpenGLTexture *tex = boundTextures_[i]; + const GLRTexture *tex = boundTextures_[i]; if (tex) { _assert_(samp); } else { @@ -1038,7 +1098,7 @@ void OpenGLContext::ApplySamplers() { } GLenum wrapS; GLenum wrapT; - if (tex->CanWrap()) { + if (tex->canWrap) { wrapS = samp->wrapU; wrapT = samp->wrapV; } else { @@ -1046,9 +1106,9 @@ void OpenGLContext::ApplySamplers() { wrapT = GL_CLAMP_TO_EDGE; } GLenum magFilt = samp->magFilt; - GLenum minFilt = tex->HasMips() ? samp->mipMinFilt : samp->minFilt; + GLenum minFilt = tex->numMips > 1 ? samp->mipMinFilt : samp->minFilt; renderManager_.SetTextureSampler(i, wrapS, wrapT, magFilt, minFilt, 0.0f); - renderManager_.SetTextureLod(i, 0.0, (float)(tex->NumMipmaps() - 1), 0.0); + renderManager_.SetTextureLod(i, 0.0, (float)(tex->numMips - 1), 0.0); } } @@ -1140,7 +1200,9 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) { void OpenGLContext::Draw(int vertexCount, int offset) { _dbg_assert_msg_(curVBuffers_[0], "Can't call Draw without a vertex buffer"); ApplySamplers(); - renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + if (curPipeline_->inputLayout) { + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + } renderManager_.Draw(curPipeline_->prim, offset, vertexCount); } @@ -1148,12 +1210,15 @@ void OpenGLContext::DrawIndexed(int vertexCount, int offset) { _dbg_assert_msg_(curVBuffers_[0], "Can't call DrawIndexed without a vertex buffer"); _dbg_assert_msg_(curIBuffer_, "Can't call DrawIndexed without an index buffer"); ApplySamplers(); - renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + if (curPipeline_->inputLayout) { + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]); + } renderManager_.BindIndexBuffer(curIBuffer_->buffer_); renderManager_.DrawIndexed(curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT, (void *)((intptr_t)curIBufferOffset_ + offset * sizeof(uint32_t))); } void OpenGLContext::DrawUP(const void *vdata, int vertexCount) { + _assert_(curPipeline_->inputLayout); int stride = curPipeline_->inputLayout->stride; size_t dataSize = stride * vertexCount; @@ -1163,7 +1228,9 @@ void OpenGLContext::DrawUP(const void *vdata, int vertexCount) { size_t offset = frameData.push->Push(vdata, dataSize, &buf); ApplySamplers(); - renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset); + if (curPipeline_->inputLayout) { + renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset); + } renderManager_.Draw(curPipeline_->prim, 0, vertexCount); } @@ -1194,7 +1261,7 @@ OpenGLInputLayout::~OpenGLInputLayout() { void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) { // TODO: This is only accurate if there's only one stream. But whatever, for now we // never use multiple streams anyway. - stride = (GLsizei)desc.bindings[0].stride; + stride = desc.bindings.empty() ? 0 : (GLsizei)desc.bindings[0].stride; std::vector entries; for (auto &attr : desc.attributes) { @@ -1231,14 +1298,18 @@ void OpenGLInputLayout::Compile(const InputLayoutDesc &desc) { entries.push_back(entry); } - inputLayout_ = render_->CreateInputLayout(entries); + if (!entries.empty()) { + inputLayout_ = render_->CreateInputLayout(entries); + } else { + inputLayout_ = nullptr; + } } Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) { CheckGLExtensions(); - OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_); - fbo->framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil); + GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil); + OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_, framebuffer); return fbo; } @@ -1248,7 +1319,7 @@ void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Render GLRRenderPassAction depth = (GLRRenderPassAction)rp.depth; GLRRenderPassAction stencil = (GLRRenderPassAction)rp.stencil; - renderManager_.BindFramebufferAsRenderTarget(fb ? fb->framebuffer : nullptr, color, depth, stencil, rp.clearColor, rp.clearDepth, rp.clearStencil, tag); + renderManager_.BindFramebufferAsRenderTarget(fb ? fb->framebuffer_ : nullptr, color, depth, stencil, rp.clearColor, rp.clearDepth, rp.clearStencil, tag); } void OpenGLContext::CopyFramebufferImage(Framebuffer *fbsrc, int srcLevel, int srcX, int srcY, int srcZ, Framebuffer *fbdst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) { @@ -1264,7 +1335,7 @@ void OpenGLContext::CopyFramebufferImage(Framebuffer *fbsrc, int srcLevel, int s if (channelBits & FB_STENCIL_BIT) aspect |= GL_STENCIL_BUFFER_BIT; } - renderManager_.CopyFramebuffer(src->framebuffer, GLRect2D{ srcX, srcY, width, height }, dst->framebuffer, GLOffset2D{ dstX, dstY }, aspect, tag); + renderManager_.CopyFramebuffer(src->framebuffer_, GLRect2D{ srcX, srcY, width, height }, dst->framebuffer_, GLOffset2D{ dstX, dstY }, aspect, tag); } bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *fbdst, int dstX1, int dstY1, int dstX2, int dstY2, int channels, FBBlitFilter linearFilter, const char *tag) { @@ -1278,7 +1349,7 @@ bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, in if (channels & FB_STENCIL_BIT) aspect |= GL_STENCIL_BUFFER_BIT; - renderManager_.BlitFramebuffer(src->framebuffer, GLRect2D{ srcX1, srcY1, srcX2 - srcX1, srcY2 - srcY1 }, dst->framebuffer, GLRect2D{ dstX1, dstY1, dstX2 - dstX1, dstY2 - dstY1 }, aspect, linearFilter == FB_BLIT_LINEAR, tag); + renderManager_.BlitFramebuffer(src->framebuffer_, GLRect2D{ srcX1, srcY1, srcX2 - srcX1, srcY2 - srcY1 }, dst->framebuffer_, GLRect2D{ dstX1, dstY1, dstX2 - dstX1, dstY2 - dstY1 }, aspect, linearFilter == FB_BLIT_LINEAR, tag); return true; } @@ -1286,20 +1357,26 @@ void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBCh OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; GLuint aspect = 0; - if (channelBit & FB_COLOR_BIT) + if (channelBit & FB_COLOR_BIT) { aspect |= GL_COLOR_BUFFER_BIT; - if (channelBit & FB_DEPTH_BIT) + boundTextures_[binding] = &fb->framebuffer_->color_texture; + } + if (channelBit & FB_DEPTH_BIT) { aspect |= GL_DEPTH_BUFFER_BIT; - if (channelBit & FB_STENCIL_BIT) + boundTextures_[binding] = &fb->framebuffer_->z_stencil_texture; + } + if (channelBit & FB_STENCIL_BIT) { aspect |= GL_STENCIL_BUFFER_BIT; - renderManager_.BindFramebufferAsTexture(fb->framebuffer, binding, aspect, color); + boundTextures_[binding] = &fb->framebuffer_->z_stencil_texture; + } + renderManager_.BindFramebufferAsTexture(fb->framebuffer_, binding, aspect, color); } void OpenGLContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) { OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo; if (fb) { - *w = fb->framebuffer->width; - *h = fb->framebuffer->height; + *w = fb->Width(); + *h = fb->Height(); } else { *w = targetWidth_; *h = targetHeight_; diff --git a/Common/GPU/Shader.cpp b/Common/GPU/Shader.cpp new file mode 100644 index 0000000000..bd62aaf2ed --- /dev/null +++ b/Common/GPU/Shader.cpp @@ -0,0 +1,186 @@ +#include "Common/GPU/Shader.h" + +#ifdef USE_CRT_DBG +#undef new +#endif + +#include "ext/glslang/SPIRV/GlslangToSpv.h" + +ShaderLanguageDesc::ShaderLanguageDesc(ShaderLanguage lang) { + Init(lang); +} + +void ShaderLanguageDesc::Init(ShaderLanguage lang) { + shaderLanguage = lang; + switch (lang) { + case GLSL_1xx: + // Just used in the shader test, and as a basis for the others in DetectShaderLanguage. + // The real OpenGL initialization happens in thin3d_gl.cpp. + glslVersionNumber = 110; + attribute = "attribute"; + varying_vs = "varying"; + varying_fs = "varying"; + fragColor0 = "gl_FragColor"; + fragColor1 = "fragColor1"; + texture = "texture2D"; + texelFetch = nullptr; + bitwiseOps = false; + lastFragData = nullptr; + gles = false; + forceMatrix4x4 = true; + break; + case GLSL_3xx: + // Just used in the shader test. + glslVersionNumber = 300; // GLSL ES 3.0 + varying_vs = "out"; + varying_fs = "in"; + attribute = "in"; + fragColor0 = "fragColor0"; + fragColor1 = "fragColor1"; + texture = "texture"; + texelFetch = "texelFetch"; + bitwiseOps = true; + lastFragData = nullptr; + gles = true; + forceMatrix4x4 = true; + glslES30 = true; + texelFetch = "texelFetch"; + break; + case GLSL_VULKAN: + fragColor0 = "fragColor0"; + fragColor1 = "fragColor1"; + varying_fs = "in"; + varying_vs = "out"; + attribute = "in"; + bitwiseOps = true; + framebufferFetchExtension = nullptr; + gles = false; + glslES30 = true; + glslVersionNumber = 450; + lastFragData = nullptr; + texture = "texture"; + texelFetch = "texelFetch"; + forceMatrix4x4 = false; + coefsFromBuffers = true; + break; + case HLSL_D3D9: + case HLSL_D3D11: + if (lang == HLSL_D3D11) { + fragColor0 = "outfragment.target"; + fragColor1 = "outfragment.target1"; + } else { + fragColor0 = "target"; + } + varying_fs = "in"; + varying_vs = "out"; + attribute = "in"; + bitwiseOps = lang == HLSL_D3D11; + framebufferFetchExtension = nullptr; + gles = false; + glslES30 = true; // Hm, D3D9 too? + glslVersionNumber = 0; + lastFragData = nullptr; + texture = "texture"; + texelFetch = "texelFetch"; + forceMatrix4x4 = false; + coefsFromBuffers = true; + vsOutPrefix = "Out."; + viewportYSign = "-"; + break; + } +} + +void init_resources(TBuiltInResource &Resources) { + Resources.maxLights = 32; + Resources.maxClipPlanes = 6; + Resources.maxTextureUnits = 32; + Resources.maxTextureCoords = 32; + Resources.maxVertexAttribs = 64; + Resources.maxVertexUniformComponents = 4096; + Resources.maxVaryingFloats = 64; + Resources.maxVertexTextureImageUnits = 32; + Resources.maxCombinedTextureImageUnits = 80; + Resources.maxTextureImageUnits = 32; + Resources.maxFragmentUniformComponents = 4096; + Resources.maxDrawBuffers = 32; + Resources.maxVertexUniformVectors = 128; + Resources.maxVaryingVectors = 8; + Resources.maxFragmentUniformVectors = 16; + Resources.maxVertexOutputVectors = 16; + Resources.maxFragmentInputVectors = 15; + Resources.minProgramTexelOffset = -8; + Resources.maxProgramTexelOffset = 7; + Resources.maxClipDistances = 8; + Resources.maxComputeWorkGroupCountX = 65535; + Resources.maxComputeWorkGroupCountY = 65535; + Resources.maxComputeWorkGroupCountZ = 65535; + Resources.maxComputeWorkGroupSizeX = 1024; + Resources.maxComputeWorkGroupSizeY = 1024; + Resources.maxComputeWorkGroupSizeZ = 64; + Resources.maxComputeUniformComponents = 1024; + Resources.maxComputeTextureImageUnits = 16; + Resources.maxComputeImageUniforms = 8; + Resources.maxComputeAtomicCounters = 8; + Resources.maxComputeAtomicCounterBuffers = 1; + Resources.maxVaryingComponents = 60; + Resources.maxVertexOutputComponents = 64; + Resources.maxGeometryInputComponents = 64; + Resources.maxGeometryOutputComponents = 128; + Resources.maxFragmentInputComponents = 128; + Resources.maxImageUnits = 8; + Resources.maxCombinedImageUnitsAndFragmentOutputs = 8; + Resources.maxCombinedShaderOutputResources = 8; + Resources.maxImageSamples = 0; + Resources.maxVertexImageUniforms = 0; + Resources.maxTessControlImageUniforms = 0; + Resources.maxTessEvaluationImageUniforms = 0; + Resources.maxGeometryImageUniforms = 0; + Resources.maxFragmentImageUniforms = 8; + Resources.maxCombinedImageUniforms = 8; + Resources.maxGeometryTextureImageUnits = 16; + Resources.maxGeometryOutputVertices = 256; + Resources.maxGeometryTotalOutputComponents = 1024; + Resources.maxGeometryUniformComponents = 1024; + Resources.maxGeometryVaryingComponents = 64; + Resources.maxTessControlInputComponents = 128; + Resources.maxTessControlOutputComponents = 128; + Resources.maxTessControlTextureImageUnits = 16; + Resources.maxTessControlUniformComponents = 1024; + Resources.maxTessControlTotalOutputComponents = 4096; + Resources.maxTessEvaluationInputComponents = 128; + Resources.maxTessEvaluationOutputComponents = 128; + Resources.maxTessEvaluationTextureImageUnits = 16; + Resources.maxTessEvaluationUniformComponents = 1024; + Resources.maxTessPatchComponents = 120; + Resources.maxPatchVertices = 32; + Resources.maxTessGenLevel = 64; + Resources.maxViewports = 16; + Resources.maxVertexAtomicCounters = 0; + Resources.maxTessControlAtomicCounters = 0; + Resources.maxTessEvaluationAtomicCounters = 0; + Resources.maxGeometryAtomicCounters = 0; + Resources.maxFragmentAtomicCounters = 8; + Resources.maxCombinedAtomicCounters = 8; + Resources.maxAtomicCounterBindings = 1; + Resources.maxVertexAtomicCounterBuffers = 0; + Resources.maxTessControlAtomicCounterBuffers = 0; + Resources.maxTessEvaluationAtomicCounterBuffers = 0; + Resources.maxGeometryAtomicCounterBuffers = 0; + Resources.maxFragmentAtomicCounterBuffers = 1; + Resources.maxCombinedAtomicCounterBuffers = 1; + Resources.maxAtomicCounterBufferSize = 16384; + Resources.maxTransformFeedbackBuffers = 4; + Resources.maxTransformFeedbackInterleavedComponents = 64; + Resources.maxCullDistances = 8; + Resources.maxCombinedClipAndCullDistances = 8; + Resources.maxSamples = 4; + Resources.limits.nonInductiveForLoops = 1; + Resources.limits.whileLoops = 1; + Resources.limits.doWhileLoops = 1; + Resources.limits.generalUniformIndexing = 1; + Resources.limits.generalAttributeMatrixVectorIndexing = 1; + Resources.limits.generalVaryingIndexing = 1; + Resources.limits.generalSamplerIndexing = 1; + Resources.limits.generalVariableIndexing = 1; + Resources.limits.generalConstantMatrixVectorIndexing = 1; +} diff --git a/Common/GPU/Shader.h b/Common/GPU/Shader.h new file mode 100644 index 0000000000..20bf482239 --- /dev/null +++ b/Common/GPU/Shader.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include // for size_t + +// GLSL_1xx and GLSL_3xx each cover a lot of sub variants. All the little quirks +// that differ are covered in ShaderLanguageDesc. +// Defined as a bitmask so stuff like GetSupportedShaderLanguages can return combinations. +enum ShaderLanguage { + GLSL_1xx = 1, + GLSL_3xx = 2, + GLSL_VULKAN = 4, + HLSL_D3D9 = 8, + HLSL_D3D11 = 16, +}; + +inline bool ShaderLanguageIsOpenGL(ShaderLanguage lang) { + return lang == GLSL_1xx || lang == GLSL_3xx; +} + +enum class ShaderStage { + Vertex, + Fragment, + Geometry, + Compute, +}; + +struct ShaderLanguageDesc { + ShaderLanguageDesc() {} + explicit ShaderLanguageDesc(ShaderLanguage lang); + + void Init(ShaderLanguage lang); + + int glslVersionNumber = 0; + ShaderLanguage shaderLanguage; + bool gles = false; + const char *varying_fs = nullptr; + const char *varying_vs = nullptr; + const char *attribute = nullptr; + const char *fragColor0 = nullptr; + const char *fragColor1 = nullptr; + const char *texture = nullptr; + const char *texelFetch = nullptr; + const char *lastFragData = nullptr; + const char *framebufferFetchExtension = nullptr; + const char *vsOutPrefix = ""; + const char *viewportYSign = ""; + bool glslES30 = false; // really glslES30Features. TODO: Clean this up. + bool bitwiseOps = false; + bool forceMatrix4x4 = false; + bool coefsFromBuffers = false; + char driverInfo[128]; // Really only GL uses this. +}; + +enum class UniformType : int8_t { + FLOAT1, + FLOAT2, + FLOAT3, + FLOAT4, + MATRIX4X4, +}; + +// Describe uniforms intricately enough that we can support them on all backends. +// This will generate a uniform struct on the newer backends and individual uniforms on the older ones. +struct UniformDesc { + const char *name; // For GL + int16_t vertexReg; // For D3D + int16_t fragmentReg; // For D3D + UniformType type; + int16_t offset; + // TODO: Support array elements etc. +}; + +struct UniformBufferDesc { + size_t uniformBufferSize; + std::vector uniforms; +}; + +// For passing error messages from shader compilation (and other critical issues) back to the host. +// This can run on any thread - be aware! +// TODO: See if we can find a less generic name for this. +typedef void (*ErrorCallbackFn)(const char *shortDesc, const char *details, void *userdata); diff --git a/GPU/Common/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp similarity index 84% rename from GPU/Common/ShaderTranslation.cpp rename to Common/GPU/ShaderTranslation.cpp index 25edef6297..229f0e29e5 100644 --- a/GPU/Common/ShaderTranslation.cpp +++ b/Common/GPU/ShaderTranslation.cpp @@ -36,8 +36,9 @@ #include "Common/Log.h" #include "Common/StringUtils.h" +#include "Common/GPU/Shader.h" -#include "GPU/Common/ShaderTranslation.h" +#include "Common/GPU/ShaderTranslation.h" #include "ext/glslang/SPIRV/GlslangToSpv.h" #include "Common/GPU/thin3d.h" #include "Common/GPU/OpenGL/GLFeatures.h" @@ -52,14 +53,12 @@ extern void init_resources(TBuiltInResource &Resources); -static EShLanguage GetLanguage(const Draw::ShaderStage stage) { +static EShLanguage GetLanguage(const ShaderStage stage) { switch (stage) { - case Draw::ShaderStage::VERTEX: return EShLangVertex; - case Draw::ShaderStage::CONTROL: return EShLangTessControl; - case Draw::ShaderStage::EVALUATION: return EShLangTessEvaluation; - case Draw::ShaderStage::GEOMETRY: return EShLangGeometry; - case Draw::ShaderStage::FRAGMENT: return EShLangFragment; - case Draw::ShaderStage::COMPUTE: return EShLangCompute; + case ShaderStage::Vertex: return EShLangVertex; + case ShaderStage::Geometry: return EShLangGeometry; + case ShaderStage::Fragment: return EShLangFragment; + case ShaderStage::Compute: return EShLangCompute; default: return EShLangVertex; } } @@ -76,7 +75,7 @@ void ShaderTranslationShutdown() { #endif } -std::string Preprocess(std::string code, ShaderLanguage lang, Draw::ShaderStage stage) { +std::string Preprocess(std::string code, ShaderLanguage lang, ShaderStage stage) { // This takes GL up to the version we need. return code; } @@ -125,8 +124,8 @@ float u_video : register(c5); // Also we need to rip out single uniforms and replace them with blocks. // Should probably do it in the source shader instead and then back translate to old style GLSL, but // SPIRV-Cross currently won't compile with the Android NDK so I can't be bothered. -std::string Postprocess(std::string code, ShaderLanguage lang, Draw::ShaderStage stage) { - if (lang != HLSL_D3D11 && lang != HLSL_DX9) +std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage) { + if (lang != HLSL_D3D11 && lang != HLSL_D3D9) return code; std::stringstream out; @@ -134,18 +133,18 @@ std::string Postprocess(std::string code, ShaderLanguage lang, Draw::ShaderStage // Output the uniform buffer. if (lang == HLSL_D3D11) out << cbufferDecl; - else if (lang == HLSL_DX9) + else if (lang == HLSL_D3D9) out << d3d9RegisterDecl; // Alright, now let's go through it line by line and zap the single uniforms. std::string line; std::stringstream instream(code); while (std::getline(instream, line)) { - if (line == "uniform sampler2D sampler0;" && lang == HLSL_DX9) { + if (line == "uniform sampler2D sampler0;" && lang == HLSL_D3D9) { out << "sampler2D sampler0 : register(s0);\n"; continue; } - if (line == "uniform sampler2D sampler1;" && lang == HLSL_DX9) { + if (line == "uniform sampler2D sampler1;" && lang == HLSL_D3D9) { out << "sampler2D sampler1 : register(s1);\n"; continue; } @@ -158,24 +157,24 @@ std::string Postprocess(std::string code, ShaderLanguage lang, Draw::ShaderStage return output; } -bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, Draw::ShaderStage stage, std::string *errorMessage) { +bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, ShaderStage stage, std::string *errorMessage) { std::stringstream out; static struct { - Draw::ShaderStage stage; + ShaderStage stage; const char *needle; const char *replacement; } replacements[] = { - { Draw::ShaderStage::VERTEX, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" }, - { Draw::ShaderStage::VERTEX, "attribute vec2 a_texcoord0;", "layout(location = 2) in vec2 a_texcoord0;"}, - { Draw::ShaderStage::VERTEX, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" }, - { Draw::ShaderStage::FRAGMENT, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" }, - { Draw::ShaderStage::FRAGMENT, "texture2D(", "texture(" }, - { Draw::ShaderStage::FRAGMENT, "gl_FragColor", "fragColor0" }, + { ShaderStage::Vertex, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" }, + { ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 2) in vec2 a_texcoord0;"}, + { ShaderStage::Vertex, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" }, + { ShaderStage::Fragment, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" }, + { ShaderStage::Fragment, "texture2D(", "texture(" }, + { ShaderStage::Fragment, "gl_FragColor", "fragColor0" }, }; out << vulkanPrologue; - if (stage == Draw::ShaderStage::FRAGMENT) { + if (stage == ShaderStage::Fragment) { out << "layout (location = 0) out vec4 fragColor0;\n"; } // Output the uniform buffer. @@ -197,7 +196,7 @@ bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetada } else if (line.find("uniform ") != std::string::npos) { continue; } else if (2 == sscanf(line.c_str(), "varying vec%d v_texcoord%d;", &vecSize, &num)) { - if (stage == Draw::ShaderStage::FRAGMENT) { + if (stage == ShaderStage::Fragment) { line = StringFromFormat("layout(location = %d) in vec%d v_texcoord%d;", num, vecSize, num); } else { line = StringFromFormat("layout(location = %d) out vec%d v_texcoord%d;", num, vecSize, num); @@ -218,11 +217,11 @@ bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetada return true; } -bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, Draw::ShaderStage stage, std::string *errorMessage) { - if (srcLang != GLSL_300 && srcLang != GLSL_140) +bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, ShaderStage stage, std::string *errorMessage) { + if (srcLang != GLSL_3xx && srcLang != GLSL_1xx) return false; - if ((srcLang == GLSL_140 || srcLang == GLSL_300) && destLang == GLSL_VULKAN) { + if ((srcLang == GLSL_1xx || srcLang == GLSL_3xx) && destLang == GLSL_VULKAN) { // Let's just mess about at the string level, no need to recompile. bool result = ConvertToVulkanGLSL(dest, destMetadata, src, stage, errorMessage); return result; @@ -291,7 +290,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade switch (destLang) { #ifdef _WIN32 - case HLSL_DX9: + case HLSL_D3D9: { spirv_cross::CompilerHLSL hlsl(spirv); spirv_cross::CompilerHLSL::Options options{}; @@ -326,7 +325,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade return true; } #endif - case GLSL_140: + case GLSL_1xx: { spirv_cross::CompilerGLSL glsl(std::move(spirv)); // The SPIR-V is now parsed, and we can perform reflection on it. @@ -351,7 +350,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, TranslatedShade *dest = glsl.compile(); return true; } - case GLSL_300: + case GLSL_3xx: { spirv_cross::CompilerGLSL glsl(std::move(spirv)); // The SPIR-V is now parsed, and we can perform reflection on it. diff --git a/GPU/Common/ShaderTranslation.h b/Common/GPU/ShaderTranslation.h similarity index 90% rename from GPU/Common/ShaderTranslation.h rename to Common/GPU/ShaderTranslation.h index 9d4e41e3f1..3f1a80d3d6 100644 --- a/GPU/Common/ShaderTranslation.h +++ b/Common/GPU/ShaderTranslation.h @@ -18,7 +18,8 @@ #pragma once #include -#include "GPU/Common/ShaderCommon.h" + +#include "Common/GPU/Shader.h" #include "Common/GPU/thin3d.h" struct TranslatedShaderMetadata { @@ -28,4 +29,4 @@ struct TranslatedShaderMetadata { void ShaderTranslationInit(); void ShaderTranslationShutdown(); -bool TranslateShader(std::string *dst, ShaderLanguage destLang, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, Draw::ShaderStage stage, std::string *errorMessage); +bool TranslateShader(std::string *dst, ShaderLanguage destLang, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, ShaderStage stage, std::string *errorMessage); diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp new file mode 100644 index 0000000000..4db6bd8577 --- /dev/null +++ b/Common/GPU/ShaderWriter.cpp @@ -0,0 +1,313 @@ +#include +#include +#include + +#include "Common/GPU/Shader.h" +#include "Common/GPU/ShaderWriter.h" + +const char *vulkan_glsl_preamble_fs = +"#version 450\n" +"#extension GL_ARB_separate_shader_objects : enable\n" +"#extension GL_ARB_shading_language_420pack : enable\n" +"#extension GL_ARB_conservative_depth : enable\n" +"#extension GL_ARB_shader_image_load_store : enable\n" +"#define splat3(x) vec3(x)\n" +"#define DISCARD discard\n" +"precision lowp float;\n" +"precision highp int;\n" +"\n"; + +const char *hlsl_preamble_fs = +"#define vec2 float2\n" +"#define vec3 float3\n" +"#define vec4 float4\n" +"#define uvec3 uint3\n" +"#define uvec4 uint4\n" +"#define ivec3 int3\n" +"#define ivec4 int4\n" +"#define mat4 float4x4\n" +"#define mat3x4 float4x3\n" // note how the conventions are backwards +"#define splat3(x) float3(x, x, x)\n" +"#define mix lerp\n" +"#define lowp\n" +"#define mediump\n" +"#define highp\n" +"#define mod(x, y) fmod(x, y)\n"; + +const char *hlsl_d3d11_preamble_fs = +"#define DISCARD discard\n" +"#define DISCARD_BELOW(x) clip(x);\n"; +const char *hlsl_d3d9_preamble_fs = +"#define DISCARD clip(-1)\n" +"#define DISCARD_BELOW(x) clip(x)\n"; + +const char *vulkan_glsl_preamble_vs = +"#version 450\n" +"#extension GL_ARB_separate_shader_objects : enable\n" +"#extension GL_ARB_shading_language_420pack : enable\n" +"#define mul(x, y) ((x) * (y))\n" +"#define splat3(x) vec3(x)\n" +"precision highp float;\n" +"\n"; + +const char *hlsl_preamble_vs = +"#define vec2 float2\n" +"#define vec3 float3\n" +"#define vec4 float4\n" +"#define ivec2 int2\n" +"#define ivec4 int4\n" +"#define mat4 float4x4\n" +"#define mat3x4 float4x3\n" // note how the conventions are backwards +"#define splat3(x) vec3(x, x, x)\n" +"#define lowp\n" +"#define mediump\n" +"#define highp\n" +"\n"; + +// Unsafe. But doesn't matter, we'll use big buffers for shader gen. +ShaderWriter & ShaderWriter::F(const char *format, ...) { + va_list args; + va_start(args, format); + p_ += vsprintf(p_, format, args); + va_end(args); + return *this; +} + +void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions) { + switch (lang_.shaderLanguage) { + case GLSL_VULKAN: + switch (stage_) { + case ShaderStage::Vertex: + W(vulkan_glsl_preamble_vs); + break; + case ShaderStage::Fragment: + W(vulkan_glsl_preamble_fs); + break; + } + break; + case HLSL_D3D11: + case HLSL_D3D9: + switch (stage_) { + case ShaderStage::Vertex: + W(hlsl_preamble_vs); + break; + case ShaderStage::Fragment: + W(hlsl_preamble_fs); + if (lang_.shaderLanguage == HLSL_D3D9) { + W(hlsl_d3d9_preamble_fs); + } else { + W(hlsl_d3d11_preamble_fs); + } + break; + } + break; + default: // OpenGL + F("#version %d%s\n", lang_.glslVersionNumber, lang_.gles && lang_.glslES30 ? " es" : ""); + // IMPORTANT! Extensions must be the first thing after #version. + for (size_t i = 0; i < num_gl_extensions; i++) { + F("%s\n", gl_extensions[i]); + } + // Print some system info - useful to gather information directly from screenshots. + F("// %s\n", lang_.driverInfo); + switch (stage_) { + case ShaderStage::Fragment: + C("#define DISCARD discard\n"); + if (lang_.gles) { + C("precision lowp float;\n"); + if (lang_.glslES30) { + C("precision highp int;\n"); + } + } + break; + case ShaderStage::Vertex: + if (lang_.gles) { + C("precision highp float;\n"); + } + C("#define gl_VertexIndex gl_VertexID\n"); + break; + } + if (!lang_.gles) { + C("#define lowp\n"); + C("#define mediump\n"); + C("#define highp\n"); + } + C("#define splat3(x) vec3(x)\n"); + C("#define mul(x, y) ((x) * (y))\n"); + break; + } +} + +void ShaderWriter::BeginVSMain(Slice inputs, Slice uniforms, Slice varyings) { + _assert_(this->stage_ == ShaderStage::Vertex); + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + case HLSL_D3D9: + { + C("struct VS_OUTPUT {\n"); + for (auto &varying : varyings) { + F(" %s %s : %s;\n", varying.type, varying.name, varying.semantic); + } + F(" vec4 pos : %s;\n", lang_.shaderLanguage == HLSL_D3D11 ? "SV_Position" : "POSITION"); + C("};\n"); + + C("VS_OUTPUT main( "); // 2 spaces for the D3D9 rewind + if (lang_.shaderLanguage == HLSL_D3D11) { + C("uint gl_VertexIndex : SV_VertexID, "); + } + Rewind(2); // Get rid of the last comma. + C(") {\n"); + C(" vec4 gl_Position;\n"); + for (auto &varying : varyings) { + F(" %s %s;\n", varying.type, varying.name); + } + break; + } + case GLSL_VULKAN: + for (auto &varying : varyings) { + F("layout(location = %d) %s out %s %s; // %s\n", + varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, varying.semantic); + } + C("void main() {\n"); + break; + default: // OpenGL + for (auto &varying : varyings) { + F("%s %s %s %s; // %s (%d)\n", lang_.varying_vs, varying.precision ? varying.precision : "", varying.type, varying.name, varying.semantic, varying.index); + } + C("void main() {\n"); + break; + } +} + +void ShaderWriter::BeginFSMain(Slice uniforms, Slice varyings) { + _assert_(this->stage_ == ShaderStage::Fragment); + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + if (!uniforms.is_empty()) { + for (auto &uniform : uniforms) { + //F(" %s %s : %s;\n", uniform.type, uniform.name, uniform.index); + } + } + // Let's do the varyings as parameters to main, no struct. + C("vec4 main("); + for (auto &varying : varyings) { + F(" %s %s : %s, ", varying.type, varying.name, varying.semantic); + } + // Erase the last comma + Rewind(2); + + F(") : SV_Target0 {\n"); + break; + case HLSL_D3D9: + for (auto &uniform : uniforms) { + F(" %s %s : %s;\n", uniform.type, uniform.name, uniform.index); + } + // Let's do the varyings as parameters to main, no struct. + C("vec4 main("); + for (auto &varying : varyings) { + F(" %s %s : %s, ", varying.type, varying.name, varying.semantic); + } + // Erase the last comma + Rewind(2); + + F(") : COLOR {\n"); + break; + case GLSL_VULKAN: + for (auto &varying : varyings) { + F("layout(location = %d) %s in %s %s; // %s\n", varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, varying.semantic); + } + C("layout(location = 0, index = 0) out vec4 fragColor0;\n"); + C("\nvoid main() {\n"); + break; + default: + for (auto &varying : varyings) { + F("%s %s %s %s; // %s\n", lang_.varying_fs, varying.precision ? varying.precision : "", varying.type, varying.name, varying.semantic); + } + if (!strcmp(lang_.fragColor0, "fragColor0")) { + C("out vec4 fragColor0;\n"); + } + C("\nvoid main() {\n"); + break; + } +} + +void ShaderWriter::EndVSMain(Slice varyings) { + _assert_(this->stage_ == ShaderStage::Vertex); + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + case HLSL_D3D9: + C(" VS_OUTPUT vs_out;\n"); + C(" vs_out.pos = gl_Position;\n"); + for (auto &varying : varyings) { + F(" vs_out.%s = %s;\n", varying.name, varying.name); + } + C(" return vs_out;\n"); + break; + case GLSL_VULKAN: + default: // OpenGL + break; + } + C("}\n"); +} + +void ShaderWriter::EndFSMain(const char *vec4_color_variable) { + _assert_(this->stage_ == ShaderStage::Fragment); + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + case HLSL_D3D9: + F(" return %s;\n", vec4_color_variable); + break; + case GLSL_VULKAN: + default: // OpenGL + F(" %s = %s;\n", lang_.fragColor0, vec4_color_variable); + break; + } + C("}\n"); +} + +void ShaderWriter::HighPrecisionFloat() { + if ((ShaderLanguageIsOpenGL(lang_.shaderLanguage) && lang_.gles) || lang_.shaderLanguage == GLSL_VULKAN) { + C("precision highp float;\n"); + } +} + +void ShaderWriter::DeclareTexture2D(const char *name, int binding) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("Texture2D %s : register(t%d);\n", name, binding); + break; + case HLSL_D3D9: + break; + case GLSL_VULKAN: + // In the thin3d descriptor set layout, textures start at 1 in set 0. Hence the +1. + F("layout(set = 0, binding = %d) uniform sampler2D %s;\n", binding + 1, name); + break; + default: + F("uniform sampler2D %s;\n", name); + break; + } +} + +void ShaderWriter::DeclareSampler2D(const char *name, int binding) { + // We only use separate samplers in HLSL D3D11, where we have no choice. + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("SamplerState %s : register(s%d);\n", name, binding); + break; + } +} + +ShaderWriter &ShaderWriter::SampleTexture2D(const char *texName, const char *samplerName, const char *uv) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("%s.Sample(%s, %s)", texName, samplerName, uv); + break; + case HLSL_D3D9: + F("tex2D(%s, %s)", texName, uv); + break; + default: + // Note: we ignore the sampler. make sure you bound samplers to the textures correctly. + F("%s(%s, %s)", lang_.texture, texName, uv); + break; + } + return *this; +} diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h new file mode 100644 index 0000000000..3aa1a4df29 --- /dev/null +++ b/Common/GPU/ShaderWriter.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include "Common/Log.h" +#include "Common/GPU/Shader.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUCommon.h" +#include "Common/Data/Collections/Slice.h" + +#include "Common/GPU/thin3d.h" + +// Helps generate a shader compatible with all backends. +// +// Can use the uniform buffer support in thin3d. +// +// Using #defines and magic in this class, we partially define our own shader language that basically looks +// like GLSL, but has a few little oddities like splat3. + +struct InputDef { + const char *type; + const char *name; +}; + +struct UniformDef { + const char *type; + const char *name; + int index; +}; + +struct VaryingDef { + const char *type; + const char *name; + const char *semantic; + int index; + const char *precision; +}; + +class ShaderWriter { +public: + ShaderWriter(char *buffer, const ShaderLanguageDesc &lang, ShaderStage stage, const char **gl_extensions, size_t num_gl_extensions) : p_(buffer), lang_(lang), stage_(stage) { + Preamble(gl_extensions, num_gl_extensions); + } + + // I tried to call all three write functions "W", but only MSVC + // managed to disentangle the ambiguities, so had to give up on that. + + // Assumes the input is zero-terminated. + // C : Copies a buffer directly to the stream. + template + ShaderWriter &C(const char(&text)[T]) { + memcpy(p_, text, T); + p_ += T - 1; + return *this; + } + // W: Writes a zero-terminated string to the stream. + ShaderWriter &W(const char *text) { + size_t len = strlen(text); + memcpy(p_, text, len + 1); + p_ += len; + return *this; + } + + // F: Formats into the buffer. + ShaderWriter &F(const char *format, ...); + + // Useful for fragment shaders in GLES. + // We always default integers to high precision. + void HighPrecisionFloat(); + + // Several of the shader languages ignore samplers, beware of that. + void DeclareSampler2D(const char *name, int binding); + void DeclareTexture2D(const char *name, int binding); + + ShaderWriter &SampleTexture2D(const char *texName, const char *samplerName, const char *uv); + + // Simple shaders with no special tricks. + void BeginVSMain(Slice inputs, Slice uniforms, Slice varyings); + void BeginFSMain(Slice uniforms, Slice varyings); + + // For simple shaders that output a single color, we can deal with this generically. + void EndVSMain(Slice varyings); + void EndFSMain(const char *vec4_color_variable); + + + void Rewind(size_t offset) { + p_ -= offset; + } + + // Can probably remove this + char *GetPos() { + return p_; + } + +private: + void Preamble(const char **gl_extensions, size_t num_gl_extensions); + + char *p_; + const ShaderLanguageDesc &lang_; + const ShaderStage stage_; +}; diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index ef139e4ca7..bf77c2720f 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -146,10 +146,14 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { } } + // Temporary hack for libretro. For some reason, when we try to load the functions from this extension, + // we get null pointers when running libretro. Quite strange. +#if !defined(__LIBRETRO__) if (IsInstanceExtensionAvailable(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) { instance_extensions_enabled_.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); extensionsLookup_.KHR_get_physical_device_properties2 = true; } +#endif // Validate that all the instance extensions we ask for are actually available. for (auto ext : instance_extensions_enabled_) { @@ -537,9 +541,13 @@ void VulkanContext::ChooseDevice(int physical_device) { break; } } - if (deviceInfo_.preferredDepthStencilFormat == VK_FORMAT_UNDEFINED) { - // WTF? This is bad. - ERROR_LOG(G3D, "Could not find a usable depth stencil format."); + + _assert_msg_(deviceInfo_.preferredDepthStencilFormat != VK_FORMAT_UNDEFINED, "Could not find a usable depth stencil format."); + VkFormatProperties preferredProps; + vkGetPhysicalDeviceFormatProperties(physical_devices_[physical_device_], deviceInfo_.preferredDepthStencilFormat, &preferredProps); + if ((preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) && + (preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) { + deviceInfo_.canBlitToPreferredDepthStencilFormat = true; } // This is as good a place as any to do this. @@ -915,6 +923,11 @@ static std::string surface_transforms_to_string(VkSurfaceTransformFlagsKHR trans bool VulkanContext::InitSwapchain() { VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_); + if (res == VK_ERROR_SURFACE_LOST_KHR) { + // Not much to do. + ERROR_LOG(G3D, "VK: Surface lost in InitSwapchain"); + return false; + } _dbg_assert_(res == VK_SUCCESS); uint32_t presentModeCount; res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, nullptr); @@ -1122,24 +1135,6 @@ void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int VkImageLayout oldImageLayout, VkImageLayout newImageLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask) { -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - if (aspectMask == VK_IMAGE_ASPECT_COLOR_BIT) { - // Hack to disable transaction elimination on ARM Mali. - if (oldImageLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || oldImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - oldImageLayout = VK_IMAGE_LAYOUT_GENERAL; - if (newImageLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || newImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - newImageLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - if (aspectMask != VK_IMAGE_ASPECT_COLOR_BIT) { - // Hack to disable transaction elimination on ARM Mali. - if (oldImageLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || oldImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - oldImageLayout = VK_IMAGE_LAYOUT_GENERAL; - if (newImageLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || newImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - newImageLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif VkImageMemoryBarrier image_memory_barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; image_memory_barrier.srcAccessMask = srcAccessMask; image_memory_barrier.dstAccessMask = dstAccessMask; @@ -1182,24 +1177,43 @@ EShLanguage FindLanguage(const VkShaderStageFlagBits shader_type) { // Compile a given string containing GLSL into SPV for use by VK // Return value of false means an error was encountered. -bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, - const char *pshader, - std::vector &spirv, std::string *errorMessage) { +bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, + std::vector &spirv, std::string *errorMessage) { glslang::TProgram program; const char *shaderStrings[1]; - EProfile profile = ECoreProfile; - int defaultVersion = 450; TBuiltInResource Resources; init_resources(Resources); - // Enable SPIR-V and Vulkan rules when parsing GLSL - EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); + int defaultVersion; + EShMessages messages; + EProfile profile; + + switch (variant) { + case GLSLVariant::VULKAN: + // Enable SPIR-V and Vulkan rules when parsing GLSL + messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); + defaultVersion = 450; + profile = ECoreProfile; + break; + case GLSLVariant::GL140: + messages = (EShMessages)(EShMsgDefault); + defaultVersion = 140; + profile = ECompatibilityProfile; + break; + case GLSLVariant::GLES300: + messages = (EShMessages)(EShMsgDefault); + defaultVersion = 300; + profile = EEsProfile; + break; + default: + return false; + } EShLanguage stage = FindLanguage(shader_type); glslang::TShader shader(stage); - shaderStrings[0] = pshader; + shaderStrings[0] = sourceCode; shader.setStrings(shaderStrings, 1); if (!shader.parse(&Resources, defaultVersion, profile, false, true, messages)) { diff --git a/Common/GPU/Vulkan/VulkanContext.h b/Common/GPU/Vulkan/VulkanContext.h index a14f96bdd7..3798066c65 100644 --- a/Common/GPU/Vulkan/VulkanContext.h +++ b/Common/GPU/Vulkan/VulkanContext.h @@ -52,6 +52,7 @@ enum WindowSystem { struct VulkanPhysicalDeviceInfo { VkFormat preferredDepthStencilFormat; + bool canBlitToPreferredDepthStencilFormat; }; // This is a bit repetitive... @@ -106,12 +107,6 @@ private: std::vector callbacks_; }; -// Useful for debugging on ARM Mali. This eliminates transaction elimination -// which can cause artifacts if you get barriers wrong (or if there are driver bugs). -// Cost is reduced performance on some GPU architectures. -// #define VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR -// #define VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - // VulkanContext manages the device and swapchain, and deferred deletion of objects. class VulkanContext { public: @@ -372,7 +367,14 @@ void TransitionImageLayout2(VkCommandBuffer cmd, VkImage image, int baseMip, int // GLSL compiler void init_glslang(); void finalize_glslang(); -bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *pshader, std::vector &spirv, std::string *errorMessage = nullptr); + +enum class GLSLVariant { + VULKAN, + GL140, + GLES300, +}; + +bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector &spirv, std::string *errorMessage); const char *VulkanResultToString(VkResult res); std::string FormatDriverVersion(const VkPhysicalDeviceProperties &props); diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index ff5fb56d2f..2140824ab7 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -12,8 +12,7 @@ void VulkanQueueRunner::CreateDeviceObjects() { INFO_LOG(G3D, "VulkanQueueRunner::CreateDeviceObjects"); InitBackbufferRenderPass(); - framebufferRenderPass_ = GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + framebufferRenderPass_ = GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); #if 0 // Just to check whether it makes sense to split some of these. drawidx is way bigger than the others... @@ -126,22 +125,13 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // Don't care about storing backbuffer Z - we clear it anyway. attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - attachments[1].initialLayout = VK_IMAGE_LAYOUT_GENERAL; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_GENERAL; -#else attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; -#endif attachments[1].flags = 0; VkAttachmentReference color_reference{}; color_reference.attachment = 0; -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - color_reference.layout = VK_IMAGE_LAYOUT_GENERAL; -#else color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; -#endif VkAttachmentReference depth_reference{}; depth_reference.attachment = 1; @@ -204,13 +194,8 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - attachments[0].initialLayout = VK_IMAGE_LAYOUT_GENERAL; - attachments[0].finalLayout = VK_IMAGE_LAYOUT_GENERAL; -#else - attachments[0].initialLayout = key.prevColorLayout; - attachments[0].finalLayout = key.finalColorLayout; -#endif + attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachments[0].flags = 0; attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; @@ -239,13 +224,8 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { } attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - attachments[1].initialLayout = VK_IMAGE_LAYOUT_GENERAL; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_GENERAL; -#else - attachments[1].initialLayout = key.prevDepthStencilLayout; - attachments[1].finalLayout = key.finalDepthStencilLayout; -#endif + attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[1].flags = 0; VkAttachmentReference color_reference{}; @@ -268,136 +248,12 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) { subpass.preserveAttachmentCount = 0; subpass.pPreserveAttachments = nullptr; - VkSubpassDependency deps[2]{}; - int numDeps = 0; - switch (key.prevColorLayout) { - case VK_IMAGE_LAYOUT_UNDEFINED: - // No need to specify stage or access. - break; - case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: - // Already the right color layout. Unclear that we need to do a lot here.. - break; - case VK_IMAGE_LAYOUT_GENERAL: - // We came from the Mali workaround, and are transitioning back to COLOR_ATTACHMENT_OPTIMAL. - deps[numDeps].srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - break; - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_SHADER_READ_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected color layout %d", (int)key.prevColorLayout); - break; - } - - switch (key.prevDepthStencilLayout) { - case VK_IMAGE_LAYOUT_UNDEFINED: - // No need to specify stage or access. - break; - case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: - // Already the right depth layout. Unclear that we need to do a lot here.. - break; - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_SHADER_READ_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - deps[numDeps].srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; - deps[numDeps].srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected depth layout %d", (int)key.prevDepthStencilLayout); - break; - } - - if (deps[numDeps].srcAccessMask) { - deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL; - deps[numDeps].dstSubpass = 0; - deps[numDeps].dependencyFlags = 0; - deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - deps[numDeps].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - numDeps++; - } - - // And the final transition. - // Don't need to transition it if VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. - switch (key.finalColorLayout) { - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - deps[numDeps].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - deps[numDeps].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - deps[numDeps].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_UNDEFINED: - case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: - // Nothing to do. - break; - default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected final color layout %d", (int)key.finalColorLayout); - break; - } - - switch (key.finalDepthStencilLayout) { - case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - deps[numDeps].dstAccessMask |= VK_ACCESS_SHADER_READ_BIT; - deps[numDeps].dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: - deps[numDeps].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: - deps[numDeps].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; - deps[numDeps].dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; - break; - case VK_IMAGE_LAYOUT_UNDEFINED: - case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: - // Nothing to do. - break; - default: - _dbg_assert_msg_(false, "GetRenderPass: Unexpected final depth layout %d", (int)key.finalDepthStencilLayout); - break; - } - - if (deps[numDeps].dstAccessMask) { - deps[numDeps].srcSubpass = 0; - deps[numDeps].dstSubpass = VK_SUBPASS_EXTERNAL; - deps[numDeps].dependencyFlags = 0; - deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - numDeps++; - } - VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; rp.attachmentCount = 2; rp.pAttachments = attachments; rp.subpassCount = 1; rp.pSubpasses = &subpass; - if (numDeps) { - rp.dependencyCount = numDeps; - rp.pDependencies = deps; - } - VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &pass); _assert_(res == VK_SUCCESS); _assert_(pass != VK_NULL_HANDLE); @@ -777,7 +633,9 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const { { int w = step.render.framebuffer ? step.render.framebuffer->width : vulkan_->GetBackbufferWidth(); int h = step.render.framebuffer ? step.render.framebuffer->height : vulkan_->GetBackbufferHeight(); - snprintf(buffer, sizeof(buffer), "RENDER %s (draws: %d, %dx%d, fb: %p, )", step.tag, step.render.numDraws, w, h, step.render.framebuffer); + int actual_w = step.render.renderArea.extent.width; + int actual_h = step.render.renderArea.extent.height; + snprintf(buffer, sizeof(buffer), "RENDER %s (draws: %d, %dx%d/%dx%d, fb: %p, )", step.tag, step.render.numDraws, actual_w, actual_h, w, h, step.render.framebuffer); break; } case VKRStepType::COPY: @@ -1016,6 +874,194 @@ void VulkanQueueRunner::LogReadbackImage(const VKRStep &step) { INFO_LOG(G3D, "%s", StepToString(step).c_str()); } +void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout) { + VkPipelineStageFlags srcStageMask = 0; + VkPipelineStageFlags dstStageMask = 0; + int barrierCount = 0; + VkImageMemoryBarrier barrier[2]{}; + + if (colorLayout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier[0].pNext = nullptr; + switch (colorLayout) { + case VK_IMAGE_LAYOUT_UNDEFINED: + // No need to specify stage or access. + break; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + // Already the right color layout. Unclear that we need to do a lot here.. + break; + case VK_IMAGE_LAYOUT_GENERAL: + // We came from the Mali workaround, and are transitioning back to COLOR_ATTACHMENT_OPTIMAL. + barrier[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + srcStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + break; + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + barrier[0].srcAccessMask |= VK_ACCESS_SHADER_READ_BIT; + srcStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + barrier[0].srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; + srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + barrier[0].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + default: + _dbg_assert_msg_(false, "GetRenderPass: Unexpected color layout %d", (int)colorLayout); + break; + } + dstStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier[0].oldLayout = colorLayout; + barrier[0].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier[0].image = colorImage; + barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier[0].subresourceRange.baseMipLevel = 0; + barrier[0].subresourceRange.levelCount = 1; + barrier[0].subresourceRange.layerCount = 1; + barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrierCount++; + } + + if (depthStencilImage != VK_NULL_HANDLE && depthStencilLayout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier[barrierCount].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier[barrierCount].pNext = nullptr; + switch (depthStencilLayout) { + case VK_IMAGE_LAYOUT_UNDEFINED: + // No need to specify stage or access. + break; + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + // Already the right depth layout. Unclear that we need to do a lot here.. + break; + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + barrier[barrierCount].srcAccessMask |= VK_ACCESS_SHADER_READ_BIT; + srcStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + barrier[barrierCount].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + barrier[barrierCount].srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; + srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + default: + _dbg_assert_msg_(false, "GetRenderPass: Unexpected depth layout %d", (int)depthStencilLayout); + break; + } + dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + barrier[barrierCount].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + barrier[barrierCount].oldLayout = depthStencilLayout; + barrier[barrierCount].newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + barrier[barrierCount].image = depthStencilImage; + barrier[barrierCount].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + barrier[barrierCount].subresourceRange.baseMipLevel = 0; + barrier[barrierCount].subresourceRange.levelCount = 1; + barrier[barrierCount].subresourceRange.layerCount = 1; + barrier[barrierCount].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier[barrierCount].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + + barrierCount++; + } + + if (barrierCount) { + vkCmdPipelineBarrier(cmd, srcStageMask, dstStageMask, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, nullptr, barrierCount, barrier); + } +} + +void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout colorLayout, VkImage depthStencilImage, VkImageLayout depthStencilLayout) { + VkPipelineStageFlags srcStageMask = 0; + VkPipelineStageFlags dstStageMask = 0; + + // If layouts aren't optimal, transition them. + VkImageMemoryBarrier barrier[2]{}; + + int barrierCount = 0; + if (colorLayout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier[0].pNext = nullptr; + srcStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + // And the final transition. + // Don't need to transition it if VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. + switch (colorLayout) { + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_UNDEFINED: + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + // Nothing to do. + break; + default: + _dbg_assert_msg_(false, "GetRenderPass: Unexpected final color layout %d", (int)colorLayout); + break; + } + barrier[0].oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier[0].newLayout = colorLayout; + barrier[0].image = colorImage; + barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier[0].subresourceRange.baseMipLevel = 0; + barrier[0].subresourceRange.levelCount = 1; + barrier[0].subresourceRange.layerCount = 1; + barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrierCount++; + } + + if (depthStencilImage && depthStencilLayout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier[barrierCount].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier[barrierCount].pNext = nullptr; + + srcStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + barrier[barrierCount].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + switch (depthStencilLayout) { + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + barrier[barrierCount].dstAccessMask |= VK_ACCESS_SHADER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + barrier[barrierCount].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + barrier[barrierCount].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; + break; + case VK_IMAGE_LAYOUT_UNDEFINED: + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + // Nothing to do. + break; + default: + _dbg_assert_msg_(false, "GetRenderPass: Unexpected final depth layout %d", (int)depthStencilLayout); + break; + } + barrier[barrierCount].oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + barrier[barrierCount].newLayout = depthStencilLayout; + barrier[barrierCount].image = depthStencilImage; + barrier[barrierCount].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + barrier[barrierCount].subresourceRange.baseMipLevel = 0; + barrier[barrierCount].subresourceRange.levelCount = 1; + barrier[barrierCount].subresourceRange.layerCount = 1; + barrier[barrierCount].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier[barrierCount].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrierCount++; + } + if (barrierCount) { + vkCmdPipelineBarrier(cmd, srcStageMask, dstStageMask, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, nullptr, barrierCount, barrier); + } +} + void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer cmd) { // TODO: If there are multiple, we can transition them together. @@ -1311,8 +1357,10 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c } vkCmdEndRenderPass(cmd); - // The renderpass handles the layout transition. if (fb) { + // If the desired final layout aren't the optimal layout for rendering, transition. + TransitionFromOptimal(cmd, fb->color.image, step.render.finalColorLayout, fb->depth.image, step.render.finalDepthStencilLayout); + fb->color.layout = step.render.finalColorLayout; fb->depth.layout = step.render.finalDepthStencilLayout; } @@ -1350,15 +1398,12 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->color.layout = VK_IMAGE_LAYOUT_GENERAL; } - renderPass = GetRenderPass( - step.render.color, step.render.depth, step.render.stencil, - fb->color.layout, fb->depth.layout, - step.render.finalColorLayout, - step.render.finalDepthStencilLayout); + TransitionToOptimal(cmd, fb->color.image, fb->color.layout, fb->depth.image, fb->depth.layout); - // We now do any layout pretransitions as part of the render pass. - fb->color.layout = step.render.finalColorLayout; - fb->depth.layout = step.render.finalDepthStencilLayout; + renderPass = GetRenderPass(step.render.color, step.render.depth, step.render.stencil); + + // The transition from the optimal format happens after EndRenderPass, now that we don't + // do it as part of the renderpass itself anymore. if (step.render.color == VKRRenderPassAction::CLEAR) { Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); @@ -1371,6 +1416,8 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step } } else { framebuf = backbuffer_; + + // Raw, rotated backbuffer size. w = vulkan_->GetBackbufferWidth(); h = vulkan_->GetBackbufferHeight(); renderPass = GetBackbufferRenderPass(); @@ -1383,10 +1430,20 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; rp_begin.renderPass = renderPass; rp_begin.framebuffer = framebuf; - rp_begin.renderArea.offset.x = 0; - rp_begin.renderArea.offset.y = 0; - rp_begin.renderArea.extent.width = w; - rp_begin.renderArea.extent.height = h; + + VkRect2D rc = step.render.renderArea; + if (!step.render.framebuffer) { + // Rendering to backbuffer, must rotate, just like scissors. + DisplayRect rotated_rc{ rc.offset.x, rc.offset.y, (int)rc.extent.width, (int)rc.extent.height }; + RotateRectToDisplay(rotated_rc, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight()); + + rc.offset.x = rotated_rc.x; + rc.offset.y = rotated_rc.y; + rc.extent.width = rotated_rc.w; + rc.extent.height = rotated_rc.h; + } + + rp_begin.renderArea = rc; rp_begin.clearValueCount = numClearVals; rp_begin.pClearValues = numClearVals ? clearVal : nullptr; vkCmdBeginRenderPass(cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); @@ -1580,24 +1637,6 @@ void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageMemor barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; img.layout = barrier.newLayout; - - // NOTE: Must do this AFTER updating img.layout to avoid behaviour differences. -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) { - if (barrier.oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || barrier.oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - if (barrier.newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || barrier.newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - if (aspect != VK_IMAGE_ASPECT_COLOR_BIT) { - if (barrier.oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || barrier.oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - if (barrier.newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || barrier.newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif } void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect) { @@ -1639,24 +1678,6 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageMemor barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; img.layout = barrier.newLayout; - - // NOTE: Must do this AFTER updating img.layout to avoid behaviour differences. -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) { - if (barrier.oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || barrier.oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - if (barrier.newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL || barrier.newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_DEPTH_STENCIL - if (aspect != VK_IMAGE_ASPECT_COLOR_BIT) { - if (barrier.oldLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || barrier.oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - if (barrier.newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || barrier.newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - } -#endif } void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h index ada7cc49e2..912a6044d9 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.h +++ b/Common/GPU/Vulkan/VulkanQueueRunner.h @@ -32,6 +32,13 @@ enum class VKRRenderCommand : uint8_t { NUM_RENDER_COMMANDS, }; +enum PipelineFlags { + PIPELINE_FLAG_NONE = 0, + PIPELINE_FLAG_USES_LINES = (1 << 2), + PIPELINE_FLAG_USES_BLEND_CONSTANT = (1 << 3), + PIPELINE_FLAG_USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer. +}; + struct VkRenderData { VKRRenderCommand cmd; union { @@ -143,6 +150,8 @@ struct VKRStep { int numReads; VkImageLayout finalColorLayout; VkImageLayout finalDepthStencilLayout; + u32 pipelineFlags; + VkRect2D renderArea; } render; struct { VKRFramebuffer *src; @@ -209,17 +218,12 @@ public: VKRRenderPassAction colorLoadAction; VKRRenderPassAction depthLoadAction; VKRRenderPassAction stencilLoadAction; - VkImageLayout prevColorLayout; - VkImageLayout prevDepthStencilLayout; - VkImageLayout finalColorLayout; - VkImageLayout finalDepthStencilLayout; }; // Only call this from the render thread! Also ok during initialization (LoadCache). VkRenderPass GetRenderPass( - VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction, - VkImageLayout prevColorLayout, VkImageLayout prevDepthStencilLayout, VkImageLayout finalColorLayout, VkImageLayout finalDepthStencilLayout) { - RPKey key{ colorLoadAction, depthLoadAction, stencilLoadAction, prevColorLayout, prevDepthStencilLayout, finalColorLayout, finalDepthStencilLayout }; + VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction) { + RPKey key{ colorLoadAction, depthLoadAction, stencilLoadAction }; return GetRenderPass(key); } @@ -271,6 +275,8 @@ private: VkImage backbufferImage_ = VK_NULL_HANDLE; VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE; + + // The "Compatible" render pass. Used when creating pipelines that render to "normal" framebuffers. VkRenderPass framebufferRenderPass_ = VK_NULL_HANDLE; // Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents. diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 0a60251896..33f8cd2e94 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -213,7 +213,11 @@ VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan } } -void VulkanRenderManager::CreateBackbuffers() { +bool VulkanRenderManager::CreateBackbuffers() { + if (!vulkan_->GetSwapchain()) { + ERROR_LOG(G3D, "No swapchain - can't create backbuffers"); + return false; + } VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, nullptr); _dbg_assert_(res == VK_SUCCESS); @@ -222,7 +226,7 @@ void VulkanRenderManager::CreateBackbuffers() { if (res != VK_SUCCESS) { ERROR_LOG(G3D, "vkGetSwapchainImagesKHR failed"); delete[] swapchainImages; - return; + return false; } VkCommandBuffer cmdInit = GetInitCmd(); @@ -260,8 +264,8 @@ void VulkanRenderManager::CreateBackbuffers() { if (InitDepthStencilBuffer(cmdInit)) { InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight()); } - curWidth_ = -1; - curHeight_ = -1; + curWidthRaw_ = -1; + curHeightRaw_ = -1; if (HasBackbuffers()) { VLOG("Backbuffers Created"); @@ -283,6 +287,7 @@ void VulkanRenderManager::CreateBackbuffers() { INFO_LOG(G3D, "Starting Vulkan submission thread (threadInitFrame_ = %d)", vulkan_->GetCurFrame()); thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this); } + return true; } void VulkanRenderManager::StopThread() { @@ -429,7 +434,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - frameData.profilingEnabled_ = enableProfiling; // Make sure the very last command buffer from the frame before the previous has been fully executed. if (useThread_) { @@ -442,9 +446,12 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { } VLOG("PUSH: Fencing %d", curFrame); + vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX); vkResetFences(device, 1, &frameData.fence); + // Can't set this until after the fence. + frameData.profilingEnabled_ = enableProfiling; frameData.readbackFenceUsed = false; uint64_t queryResults[MAX_TIMESTAMP_QUERIES]; @@ -497,6 +504,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling) { if (frameData.profilingEnabled_) { // For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool, // unless we want to limit ourselves to only measure the main cmd buffer. + // Later versions of Vulkan have support for clearing queries on the CPU timeline, but we don't want to rely on that. // Reserve the first two queries for initCmd. frameData.profile.timestampDescriptions.push_back("initCmd Begin"); frameData.profile.timestampDescriptions.push_back("initCmd"); @@ -524,9 +532,29 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { return frameData_[curFrame].initCmd; } +void VulkanRenderManager::EndCurRenderStep() { + // Save the accumulated pipeline flags so we can use that to configure the render pass. + // We'll often be able to avoid loading/saving the depth/stencil buffer. + if (curRenderStep_) { + curRenderStep_->render.pipelineFlags = curPipelineFlags_; + // We don't do this optimization for very small targets, probably not worth it. + if (!curRenderArea_.Empty() && (curWidth_ > 32 && curHeight_ > 32)) { + curRenderStep_->render.renderArea = curRenderArea_.ToVkRect2D(); + } else { + curRenderStep_->render.renderArea.offset = {}; + curRenderStep_->render.renderArea.extent = { (uint32_t)curWidth_, (uint32_t)curHeight_ }; + } + curRenderArea_.Reset(); + + // We no longer have a current render step. + curRenderStep_ = nullptr; + curPipelineFlags_ = 0; + } +} + void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, VKRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) { _dbg_assert_(insideFrame_); - // Eliminate dupes, instantly convert to a clear if possible. + // Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible. if (!steps_.empty() && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == fb) { u32 clearMask = 0; if (color == VKRRenderPassAction::CLEAR) { @@ -562,21 +590,26 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR data.clear.clearStencil = clearStencil; data.clear.clearMask = clearMask; curRenderStep_->commands.push_back(data); + curRenderArea_.SetRect(0, 0, curWidth_, curHeight_); } return; } } // More redundant bind elimination. - if (curRenderStep_ && curRenderStep_->commands.size() == 0 && curRenderStep_->render.color != VKRRenderPassAction::CLEAR && curRenderStep_->render.depth != VKRRenderPassAction::CLEAR && curRenderStep_->render.stencil != VKRRenderPassAction::CLEAR) { - // Can trivially kill the last empty render step. - _dbg_assert_(steps_.back() == curRenderStep_); - delete steps_.back(); - steps_.pop_back(); - curRenderStep_ = nullptr; - } - if (curRenderStep_ && curRenderStep_->commands.size() == 0) { - VLOG("Empty render step. Usually happens after uploading pixels.."); + if (curRenderStep_) { + if (curRenderStep_->commands.empty()) { + if (curRenderStep_->render.color != VKRRenderPassAction::CLEAR && curRenderStep_->render.depth != VKRRenderPassAction::CLEAR && curRenderStep_->render.stencil != VKRRenderPassAction::CLEAR) { + // Can trivially kill the last empty render step. + _dbg_assert_(steps_.back() == curRenderStep_); + delete steps_.back(); + steps_.pop_back(); + curRenderStep_ = nullptr; + } + VLOG("Empty render step. Usually happens after uploading pixels.."); + } + + EndCurRenderStep(); } // Older Mali drivers have issues with depth and stencil don't match load/clear/etc. @@ -622,11 +655,24 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR curStepHasViewport_ = false; curStepHasScissor_ = false; if (fb) { + curWidthRaw_ = fb->width; + curHeightRaw_ = fb->height; curWidth_ = fb->width; curHeight_ = fb->height; } else { - curWidth_ = vulkan_->GetBackbufferWidth(); - curHeight_ = vulkan_->GetBackbufferHeight(); + curWidthRaw_ = vulkan_->GetBackbufferWidth(); + curHeightRaw_ = vulkan_->GetBackbufferHeight(); + if (g_display_rotation == DisplayRotation::ROTATE_90 || g_display_rotation == DisplayRotation::ROTATE_270) { + curWidth_ = curHeightRaw_; + curHeight_ = curWidthRaw_; + } else { + curWidth_ = curWidthRaw_; + curHeight_ = curHeightRaw_; + } + } + + if (color == VKRRenderPassAction::CLEAR || depth == VKRRenderPassAction::CLEAR || stencil == VKRRenderPassAction::CLEAR) { + curRenderArea_.SetRect(0, 0, curWidth_, curHeight_); } // See above - we add a clear afterward if only one side for depth/stencil CLEAR/KEEP. @@ -649,6 +695,8 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, VkIma } } + EndCurRenderStep(); + VKRStep *step = new VKRStep{ VKRStepType::READBACK }; step->readback.aspectMask = aspectBits; step->readback.src = src; @@ -658,8 +706,6 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, VkIma step->tag = tag; steps_.push_back(step); - curRenderStep_ = nullptr; - FlushSync(); Draw::DataFormat srcFormat = Draw::DataFormat::UNDEFINED; @@ -704,6 +750,9 @@ bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, VkIma void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag) { _dbg_assert_(insideFrame_); + + EndCurRenderStep(); + VKRStep *step = new VKRStep{ VKRStepType::READBACK_IMAGE }; step->readback_image.image = image; step->readback_image.srcRect.offset = { x, y }; @@ -712,8 +761,6 @@ void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int step->tag = tag; steps_.push_back(step); - curRenderStep_ = nullptr; - FlushSync(); // Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM. @@ -927,6 +974,8 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten data.clear.clearMask = clearMask; curRenderStep_->commands.push_back(data); } + + curRenderArea_.SetRect(0, 0, curWidth_, curHeight_); } void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, VkImageAspectFlags aspectMask, const char *tag) { @@ -975,6 +1024,8 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, } } + EndCurRenderStep(); + VKRStep *step = new VKRStep{ VKRStepType::COPY }; step->copy.aspectMask = aspectMask; @@ -990,7 +1041,6 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, std::unique_lock lock(mutex_); steps_.push_back(step); - curRenderStep_ = nullptr; } void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, VkImageAspectFlags aspectMask, VkFilter filter, const char *tag) { @@ -1019,6 +1069,8 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, } } + EndCurRenderStep(); + VKRStep *step = new VKRStep{ VKRStepType::BLIT }; step->blit.aspectMask = aspectMask; @@ -1035,7 +1087,6 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, std::unique_lock lock(mutex_); steps_.push_back(step); - curRenderStep_ = nullptr; } VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int attachment) { @@ -1078,7 +1129,7 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in } void VulkanRenderManager::Finish() { - curRenderStep_ = nullptr; + EndCurRenderStep(); // Let's do just a bit of cleanup on render commands now. for (auto &step : steps_) { diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h index d1a781c7b3..1914f5214c 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.h +++ b/Common/GPU/Vulkan/VulkanRenderManager.h @@ -61,6 +61,53 @@ enum { MAX_TIMESTAMP_QUERIES = 128, }; +struct BoundingRect { + int x1; + int y1; + int x2; + int y2; + + BoundingRect() { + Reset(); + } + + void Reset() { + x1 = 65535; + y1 = 65535; + x2 = -65535; + y2 = -65535; + } + + bool Empty() const { + return x2 < 0; + } + + void SetRect(int x, int y, int width, int height) { + x1 = x; + y1 = y; + x2 = width; + y2 = height; + } + + void Apply(const VkRect2D &rect) { + if (rect.offset.x < x1) x1 = rect.offset.x; + if (rect.offset.y < y1) y1 = rect.offset.y; + int rect_x2 = rect.offset.x + rect.extent.width; + int rect_y2 = rect.offset.y + rect.extent.height; + if (rect_x2 > x2) x2 = rect_x2; + if (rect_y2 > y2) y2 = rect_y2; + } + + VkRect2D ToVkRect2D() const { + VkRect2D rect; + rect.offset.x = x1; + rect.offset.y = y1; + rect.extent.width = x2 - x1; + rect.extent.height = y2 - y1; + return rect; + } +}; + class VulkanRenderManager { public: VulkanRenderManager(VulkanContext *vulkan); @@ -104,11 +151,12 @@ public: void CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, VkImageAspectFlags aspectMask, const char *tag); void BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, VkImageAspectFlags aspectMask, VkFilter filter, const char *tag); - void BindPipeline(VkPipeline pipeline) { + void BindPipeline(VkPipeline pipeline, PipelineFlags flags) { _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_(pipeline != VK_NULL_HANDLE); VkRenderData data{ VKRRenderCommand::BIND_PIPELINE }; data.pipeline.pipeline = pipeline; + curPipelineFlags_ |= flags; curRenderStep_->commands.push_back(data); } @@ -130,10 +178,22 @@ public: curStepHasViewport_ = true; } - void SetScissor(const VkRect2D &rc) { + void SetScissor(VkRect2D rc) { _dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER); _dbg_assert_((int)rc.extent.width >= 0); _dbg_assert_((int)rc.extent.height >= 0); + + // Clamp to curWidth_/curHeight_. Apparently an issue. + if ((int)(rc.offset.x + rc.extent.width) > curWidth_) { + rc.extent.width = curWidth_ - rc.offset.x; + } + if ((int)(rc.offset.y + rc.extent.height) > curHeight_) { + rc.extent.height = curHeight_ - rc.offset.y; + } + _dbg_assert_((int)(rc.offset.x + rc.extent.width) <= curWidth_); + _dbg_assert_((int)(rc.offset.y + rc.extent.height) <= curHeight_); + curRenderArea_.Apply(rc); + VkRenderData data{ VKRRenderCommand::SCISSOR }; data.scissor.scissor = rc; curRenderStep_->commands.push_back(data); @@ -229,7 +289,7 @@ public: return renderStepOffset_ + (int)steps_.size(); } - void CreateBackbuffers(); + bool CreateBackbuffers(); void DestroyBackbuffers(); bool HasBackbuffers() { @@ -265,6 +325,8 @@ public: private: bool InitBackbufferFramebuffers(int width, int height); bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. + void EndCurRenderStep(); + void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); void Submit(int frame, bool triggerFence); @@ -320,14 +382,24 @@ private: int outOfDateFrames_ = 0; // Submission time state + + // Note: These are raw backbuffer-sized. Rotated. + int curWidthRaw_ = -1; + int curHeightRaw_ = -1; + + // Pre-rotation (as you'd expect). int curWidth_ = -1; int curHeight_ = -1; + bool insideFrame_ = false; // This is the offset within this frame, in case of a mid-frame sync. int renderStepOffset_ = 0; VKRStep *curRenderStep_ = nullptr; bool curStepHasViewport_ = false; bool curStepHasScissor_ = false; + u32 curPipelineFlags_ = 0; + BoundingRect curRenderArea_; + std::vector steps_; bool splitSubmit_ = false; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 502a5194f5..b5ac0fb8c1 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -171,13 +171,11 @@ public: VkShaderStageFlagBits StageToVulkan(ShaderStage stage) { switch (stage) { - case ShaderStage::VERTEX: return VK_SHADER_STAGE_VERTEX_BIT; - case ShaderStage::GEOMETRY: return VK_SHADER_STAGE_GEOMETRY_BIT; - case ShaderStage::COMPUTE: return VK_SHADER_STAGE_COMPUTE_BIT; - case ShaderStage::EVALUATION: return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; - case ShaderStage::CONTROL: return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT; + case ShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; + case ShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT; default: - case ShaderStage::FRAGMENT: return VK_SHADER_STAGE_FRAGMENT_BIT; + case ShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; } } @@ -215,7 +213,9 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con // We'll need this to free it later. source_ = (const char *)data; std::vector spirv; - if (!GLSLtoSPV(vkstage_, source_.c_str(), spirv)) { + std::string errorMessage; + if (!GLSLtoSPV(vkstage_, source_.c_str(), GLSLVariant::VULKAN, spirv, &errorMessage)) { + INFO_LOG(G3D, "Shader compile to module failed: %s", errorMessage.c_str()); return false; } @@ -245,7 +245,7 @@ public: class VKPipeline : public Pipeline { public: - VKPipeline(VulkanContext *vulkan, size_t size) : vulkan_(vulkan) { + VKPipeline(VulkanContext *vulkan, size_t size, PipelineFlags _flags) : vulkan_(vulkan), flags(_flags) { uboSize_ = (int)size; ubo_ = new uint8_t[uboSize_]; } @@ -274,6 +274,8 @@ public: VkPipeline backbufferPipeline = VK_NULL_HANDLE; VkPipeline framebufferPipeline = VK_NULL_HANDLE; + + PipelineFlags flags; int stride[4]{}; int dynamicUniformSize = 0; @@ -463,6 +465,8 @@ public: uint64_t GetNativeObject(NativeObject obj) override { switch (obj) { + case NativeObject::CONTEXT: + return (uint64_t)vulkan_; case NativeObject::FRAMEBUFFER_RENDERPASS: // Return a representative renderpass. return (uint64_t)renderManager_.GetFramebufferRenderPass(); @@ -495,7 +499,6 @@ public: void InvalidateCachedState() override; private: - VulkanTexture *GetNullTexture(); VulkanContext *vulkan_ = nullptr; @@ -758,6 +761,8 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) : vulkan_(vulkan), caps_{}, renderManager_(vulkan) { + shaderLanguageDesc_.Init(GLSL_VULKAN); + caps_.anisoSupported = vulkan->GetDeviceFeatures().enabled.samplerAnisotropy != 0; caps_.geometryShaderSupported = vulkan->GetDeviceFeatures().enabled.geometryShader != 0; caps_.tesselationShaderSupported = vulkan->GetDeviceFeatures().enabled.tessellationShader != 0; @@ -778,8 +783,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit) case VULKAN_VENDOR_NVIDIA: caps_.vendor = GPUVendor::VENDOR_NVIDIA; break; case VULKAN_VENDOR_QUALCOMM: caps_.vendor = GPUVendor::VENDOR_QUALCOMM; break; case VULKAN_VENDOR_INTEL: caps_.vendor = GPUVendor::VENDOR_INTEL; break; - default: - caps_.vendor = GPUVendor::VENDOR_UNKNOWN; + default: caps_.vendor = GPUVendor::VENDOR_UNKNOWN; break; } if (caps_.vendor == GPUVendor::VENDOR_QUALCOMM) { @@ -993,11 +997,7 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) { if (key.imageViews_[i] && key.samplers_[i] && key.samplers_[i]->GetSampler()) { imageDesc[i].imageView = key.imageViews_[i]; imageDesc[i].sampler = key.samplers_[i]->GetSampler(); -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - imageDesc[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL; -#else imageDesc[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -#endif writes[numWrites].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writes[numWrites].dstSet = descSet; writes[numWrites].dstArrayElement = 0; @@ -1018,13 +1018,24 @@ VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) { } Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) { - VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float)); VKInputLayout *input = (VKInputLayout *)desc.inputLayout; VKBlendState *blend = (VKBlendState *)desc.blend; VKDepthStencilState *depth = (VKDepthStencilState *)desc.depthStencil; VKRasterState *raster = (VKRasterState *)desc.raster; - for (int i = 0; i < (int)input->bindings.size(); i++) { - pipeline->stride[i] = input->bindings[i].stride; + + u32 pipelineFlags = 0; + if (depth->info.depthTestEnable || depth->info.stencilTestEnable) { + pipelineFlags |= PIPELINE_FLAG_USES_DEPTH_STENCIL; + } + + VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), (PipelineFlags)pipelineFlags); + + if (input) { + for (int i = 0; i < (int)input->bindings.size(); i++) { + pipeline->stride[i] = input->bindings[i].stride; + } + } else { + pipeline->stride[0] = 0; } std::vector stages; @@ -1069,6 +1080,8 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) { VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; raster->ToVulkan(&rs); + VkPipelineVertexInputStateCreateInfo emptyVisc{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + VkGraphicsPipelineCreateInfo createInfo[2]{}; for (auto &info : createInfo) { info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -1081,7 +1094,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) { info.pInputAssemblyState = &inputAssembly; info.pTessellationState = nullptr; info.pMultisampleState = &ms; - info.pVertexInputState = &input->visc; + info.pVertexInputState = input ? &input->visc : &emptyVisc; info.pRasterizationState = &rs; info.pViewportState = &vs; // Must set viewport and scissor counts even if we set the actual state dynamically. info.layout = pipelineLayout_; @@ -1340,9 +1353,9 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) { void VKContext::BindCompatiblePipeline() { VkRenderPass renderPass = renderManager_.GetCompatibleRenderPass(); if (renderPass == renderManager_.GetBackbufferRenderPass()) { - renderManager_.BindPipeline(curPipeline_->backbufferPipeline); + renderManager_.BindPipeline(curPipeline_->backbufferPipeline, curPipeline_->flags); } else { - renderManager_.BindPipeline(curPipeline_->framebufferPipeline); + renderManager_.BindPipeline(curPipeline_->framebufferPipeline, curPipeline_->flags); } } @@ -1443,7 +1456,11 @@ uint32_t VKContext::GetDataFormatSupport(DataFormat fmt) const { // use this frame's init command buffer. class VKFramebuffer : public Framebuffer { public: - VKFramebuffer(VKRFramebuffer *fb) : buf_(fb) { _assert_msg_(fb, "Null fb in VKFramebuffer constructor"); } + VKFramebuffer(VKRFramebuffer *fb) : buf_(fb) { + _assert_msg_(fb, "Null fb in VKFramebuffer constructor"); + width_ = fb->width; + height_ = fb->height; + } ~VKFramebuffer() { _assert_msg_(buf_, "Null buf_ in VKFramebuffer - double delete?"); buf_->vulkan_->Delete().QueueCallback([](void *fb) { @@ -1526,7 +1543,8 @@ void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPass void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) { VKFramebuffer *fb = (VKFramebuffer *)fbo; - // TODO: There are cases where this is okay, actually. + // TODO: There are cases where this is okay, actually. But requires layout transitions and stuff - + // we're not ready for this. _assert_(fb != curFramebuffer_); int aspect = 0; diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index 95dce26147..ce09c0356c 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -101,7 +101,7 @@ bool RefCountedObject::ReleaseAssertLast() { // The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL. static const std::vector fsTexCol = { - {ShaderLanguage::GLSL_ES_200, + {ShaderLanguage::GLSL_1xx, "#ifdef GL_ES\n" "precision lowp float;\n" "#endif\n" @@ -145,7 +145,7 @@ static const std::vector fsTexCol = { }; static const std::vector fsTexColRBSwizzle = { - {ShaderLanguage::GLSL_ES_200, + {GLSL_1xx, "#ifdef GL_ES\n" "precision lowp float;\n" "#endif\n" @@ -189,7 +189,7 @@ static const std::vector fsTexColRBSwizzle = { }; static const std::vector fsCol = { - { ShaderLanguage::GLSL_ES_200, + { GLSL_1xx, "#ifdef GL_ES\n" "precision lowp float;\n" "#endif\n" @@ -226,7 +226,7 @@ static const std::vector fsCol = { // ================================== VERTEX SHADERS static const std::vector vsCol = { - { ShaderLanguage::GLSL_ES_200, + { GLSL_1xx, "#if __VERSION__ >= 130\n" "#define attribute in\n" "#define varying out\n" @@ -288,7 +288,7 @@ const UniformBufferDesc vsColBufDesc { sizeof(VsColUB), { } }; static const std::vector vsTexCol = { - { ShaderLanguage::GLSL_ES_200, + { GLSL_1xx, "#if __VERSION__ >= 130\n" "#define attribute in\n" "#define varying out\n" @@ -367,12 +367,12 @@ ShaderModule *CreateShader(DrawContext *draw, ShaderStage stage, const std::vect } bool DrawContext::CreatePresets() { - vsPresets_[VS_TEXTURE_COLOR_2D] = CreateShader(this, ShaderStage::VERTEX, vsTexCol); - vsPresets_[VS_COLOR_2D] = CreateShader(this, ShaderStage::VERTEX, vsCol); + vsPresets_[VS_TEXTURE_COLOR_2D] = CreateShader(this, ShaderStage::Vertex, vsTexCol); + vsPresets_[VS_COLOR_2D] = CreateShader(this, ShaderStage::Vertex, vsCol); - fsPresets_[FS_TEXTURE_COLOR_2D] = CreateShader(this, ShaderStage::FRAGMENT, fsTexCol); - fsPresets_[FS_COLOR_2D] = CreateShader(this, ShaderStage::FRAGMENT, fsCol); - fsPresets_[FS_TEXTURE_COLOR_2D_RB_SWIZZLE] = CreateShader(this, ShaderStage::FRAGMENT, fsTexColRBSwizzle); + fsPresets_[FS_TEXTURE_COLOR_2D] = CreateShader(this, ShaderStage::Fragment, fsTexCol); + fsPresets_[FS_COLOR_2D] = CreateShader(this, ShaderStage::Fragment, fsCol); + fsPresets_[FS_TEXTURE_COLOR_2D_RB_SWIZZLE] = CreateShader(this, ShaderStage::Fragment, fsTexColRBSwizzle); return vsPresets_[VS_TEXTURE_COLOR_2D] && vsPresets_[VS_COLOR_2D] && fsPresets_[FS_TEXTURE_COLOR_2D] && fsPresets_[FS_COLOR_2D] && fsPresets_[FS_TEXTURE_COLOR_2D_RB_SWIZZLE]; } @@ -396,7 +396,7 @@ DrawContext::~DrawContext() { DestroyPresets(); } -// TODO: SSE/NEON +// TODO: Use the functions we have in Common/ColorConv.cpp. // Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) { // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. @@ -455,7 +455,7 @@ void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u } } -// TODO: SSE/NEON +// TODO: Use the functions we have in Common/ColorConv.cpp. // Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) { // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index e3f6390afe..d29ac91842 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -12,7 +12,8 @@ #include #include -#include "DataFormat.h" +#include "Common/GPU/DataFormat.h" +#include "Common/GPU/Shader.h" namespace Lin { class Matrix4x4; @@ -161,15 +162,6 @@ enum class TextureType : uint8_t { ARRAY2D, }; -enum class ShaderStage { - VERTEX, - FRAGMENT, - GEOMETRY, - CONTROL, // HULL - EVALUATION, // DOMAIN - COMPUTE, -}; - enum class CullMode : uint8_t { NONE, FRONT, @@ -203,15 +195,6 @@ enum class TextureAddressMode { CLAMP_TO_BORDER, }; -enum class ShaderLanguage { - GLSL_ES_200 = 1, - GLSL_ES_300 = 2, - GLSL_410 = 4, - GLSL_VULKAN = 8, - HLSL_D3D9 = 32, - HLSL_D3D11 = 64, -}; - enum FormatSupport { FMT_RENDERTARGET = 1, FMT_TEXTURE = 2, @@ -261,13 +244,6 @@ enum class NativeObject { NULL_IMAGEVIEW, }; -enum FBColorDepth { - FBO_8888, - FBO_565, - FBO_4444, - FBO_5551, -}; - enum FBChannel { FB_COLOR_BIT = 1, FB_DEPTH_BIT = 2, @@ -309,7 +285,6 @@ struct FramebufferDesc { int depth; int numColorAttachments; bool z_stencil; - FBColorDepth colorDepth; const char *tag; // For graphics debuggers }; @@ -372,6 +347,10 @@ public: class Framebuffer : public RefCountedObject { public: + int Width() { return width_; } + int Height() { return height_; } +protected: + int width_ = -1, height_ = -1; }; class Buffer : public RefCountedObject { @@ -384,7 +363,7 @@ public: int Height() { return height_; } int Depth() { return depth_; } protected: - int width_, height_, depth_; + int width_ = -1, height_ = -1, depth_ = -1; }; struct BindingDesc { @@ -406,28 +385,7 @@ struct InputLayoutDesc { class InputLayout : public RefCountedObject { }; -enum class UniformType : int8_t { - FLOAT1, - FLOAT2, - FLOAT3, - FLOAT4, - MATRIX4X4, -}; - -// For emulation of uniform buffers on D3D9/GL -struct UniformDesc { - const char *name; // For GL - int16_t vertexReg; // For D3D - int16_t fragmentReg; // For D3D - UniformType type; - int16_t offset; - // TODO: Support array elements etc. -}; - -struct UniformBufferDesc { - size_t uniformBufferSize; - std::vector uniforms; -}; +// Uniform types have moved to Shader.h. class ShaderModule : public RefCountedObject { public: @@ -521,6 +479,7 @@ struct DeviceCaps { bool framebufferBlitSupported; bool framebufferDepthCopySupported; bool framebufferDepthBlitSupported; + bool framebufferFetchSupported; std::string deviceName; // The device name to use when creating the thin3d context, to get the same one. }; @@ -573,8 +532,15 @@ public: virtual std::vector GetExtensionList() const { return std::vector(); } virtual std::vector GetDeviceList() const { return std::vector(); } + // Describes the primary shader language that this implementation prefers. + const ShaderLanguageDesc &GetShaderLanguageDesc() { + return shaderLanguageDesc_; + } + virtual uint32_t GetSupportedShaderLanguages() const = 0; + virtual void SetErrorCallback(ErrorCallbackFn callback, void *userdata) {} + // Partial pipeline state, used to create pipelines. (in practice, in d3d11 they'll use the native state objects directly). virtual DepthStencilState *CreateDepthStencilState(const DepthStencilStateDesc &desc) = 0; virtual BlendState *CreateBlendState(const BlendStateDesc &desc) = 0; @@ -686,6 +652,8 @@ protected: ShaderModule *vsPresets_[VS_MAX_PRESET]; ShaderModule *fsPresets_[FS_MAX_PRESET]; + ShaderLanguageDesc shaderLanguageDesc_; + int targetWidth_; int targetHeight_; diff --git a/Common/MemoryUtil.cpp b/Common/MemoryUtil.cpp index a6f5d5e919..7076da2fc9 100644 --- a/Common/MemoryUtil.cpp +++ b/Common/MemoryUtil.cpp @@ -18,6 +18,7 @@ #include "ppsspp_config.h" #include +#include #include "Common/Common.h" #include "Common/Log.h" diff --git a/Common/Net/Resolve.cpp b/Common/Net/Resolve.cpp index aac2cdb6fd..37fde61481 100644 --- a/Common/Net/Resolve.cpp +++ b/Common/Net/Resolve.cpp @@ -24,10 +24,12 @@ #include #endif +#include "Common/Log.h" #include "Common/TimeUtil.h" namespace net { + void Init() { #ifdef _WIN32 @@ -45,7 +47,6 @@ void Shutdown() } bool DNSResolve(const std::string &host, const std::string &service, addrinfo **res, std::string &error, DNSType type) { - #if PPSSPP_PLATFORM(SWITCH) // Force IPv4 lookups. if (type == DNSType::IPV6) { diff --git a/Common/Net/URL.h b/Common/Net/URL.h index 1a8a91677b..7fcaa0c455 100644 --- a/Common/Net/URL.h +++ b/Common/Net/URL.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #if defined(_MSC_VER) && _MSC_VER < 1900 diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h index 99d7a5c343..38fcd686c9 100644 --- a/Common/Serialize/Serializer.h +++ b/Common/Serialize/Serializer.h @@ -31,6 +31,8 @@ #include #include +#include + #include "Common/CommonTypes.h" #include "Common/Log.h" diff --git a/Common/System/System.h b/Common/System/System.h index 2d87f7399d..a95736f4fc 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -99,3 +99,5 @@ float System_GetPropertyFloat(SystemProperty prop); bool System_GetPropertyBool(SystemProperty prop); std::vector __cameraGetDeviceList(); +bool audioRecording_Available(); +bool audioRecording_State(); diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index c7c4cd6e75..273fab104b 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -74,6 +74,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "AdhocHackStopFriendClear", &flags_.AdhocHackStopFriendClear); CheckSetting(iniFile, gameID, "AdhocHackPhantasyStarOffset", &flags_.AdhocHackPhantasyStarOffset); CheckSetting(iniFile, gameID, "BlockTransferAllowCreateFB", &flags_.BlockTransferAllowCreateFB); + CheckSetting(iniFile, gameID, "IntraVRAMBlockTransferAllowCreateFB", &flags_.IntraVRAMBlockTransferAllowCreateFB); CheckSetting(iniFile, gameID, "YugiohSaveFix", &flags_.YugiohSaveFix); CheckSetting(iniFile, gameID, "ForceUMDDelay", &flags_.ForceUMDDelay); CheckSetting(iniFile, gameID, "ForceMax60FPS", &flags_.ForceMax60FPS); @@ -83,6 +84,9 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ForceSoftwareRenderer", &flags_.ForceSoftwareRenderer); CheckSetting(iniFile, gameID, "DarkStalkersPresentHack", &flags_.DarkStalkersPresentHack); CheckSetting(iniFile, gameID, "ReportSmallMemstick", &flags_.ReportSmallMemstick); + CheckSetting(iniFile, gameID, "MemstickFixedFree", &flags_.MemstickFixedFree); + CheckSetting(iniFile, gameID, "DateLimited", &flags_.DateLimited); + CheckSetting(iniFile, gameID, "ReinterpretFramebuffers", &flags_.ReinterpretFramebuffers); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 7a4ab421a2..491ce37437 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -72,6 +72,7 @@ struct CompatFlags { bool AdhocHackStopFriendClear; bool AdhocHackPhantasyStarOffset; bool BlockTransferAllowCreateFB; + bool IntraVRAMBlockTransferAllowCreateFB; bool YugiohSaveFix; bool ForceUMDDelay; bool ForceMax60FPS; @@ -81,6 +82,9 @@ struct CompatFlags { bool ForceSoftwareRenderer; bool DarkStalkersPresentHack; bool ReportSmallMemstick; + bool MemstickFixedFree; + bool DateLimited; + bool ReinterpretFramebuffers; }; class IniFile; diff --git a/Core/Config.cpp b/Core/Config.cpp index f4af6dc1c7..69906dbc66 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -467,6 +467,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("EnableStateUndo", &g_Config.bEnableStateUndo, false/*&DefaultEnableStateUndo*/, true, true), ConfigSetting("RewindFlipFrequency", &g_Config.iRewindFlipFrequency, 0, true, true), + ConfigSetting("ShowOnScreenMessage", &g_Config.bShowOnScreenMessages, true, true, false), ConfigSetting("ShowRegionOnGameIcon", &g_Config.bShowRegionOnGameIcon, false), ConfigSetting("ShowIDOnGameIcon", &g_Config.bShowIDOnGameIcon, false), ConfigSetting("GameGridScale", &g_Config.fGameGridScale, 1.0), @@ -986,7 +987,7 @@ static ConfigSetting networkSettings[] = { ConfigSetting("EnableAdhocServer", &g_Config.bEnableAdhocServer, false, true, true), ConfigSetting("proAdhocServer", &g_Config.proAdhocServer, "localhost", true, true), ConfigSetting("PortOffset", &g_Config.iPortOffset, 0, true, true), - ConfigSetting("MinTimeout", &g_Config.iMinTimeout, 1, true, true), + ConfigSetting("MinTimeout", &g_Config.iMinTimeout, 0, true, true), ConfigSetting("TCPNoDelay", &g_Config.bTCPNoDelay, false, true, true), ConfigSetting("ForcedFirstConnect", &g_Config.bForcedFirstConnect, false, true, true), ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, true, true), diff --git a/Core/Config.h b/Core/Config.h index a3828cd7da..1de176d7d3 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -247,6 +247,7 @@ public: bool bShowRegionOnGameIcon; bool bShowIDOnGameIcon; float fGameGridScale; + bool bShowOnScreenMessages; // TODO: Maybe move to a separate theme system. uint32_t uItemStyleFg; diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index a4e542bf02..abfb9e40e6 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -66,11 +66,12 @@ struct CoreParameter { std::string *collectEmuLog = nullptr; bool headLess; // Try to avoid messageboxes etc - // Internal PSP resolution + // Internal PSP rendering resolution and scale factor. + int renderScaleFactor; int renderWidth; int renderHeight; - // Actual pixel output resolution (for use by glViewport and the like) + // Actual output resolution in pixels. int pixelWidth; int pixelHeight; diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 942236c68a..93863d7c6d 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -410,8 +410,10 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop) for (int i = 0; i < header->e_phnum; i++) { const Elf32_Phdr *p = &segments[i]; if (p->p_type == PT_LOAD) { - if (p->p_vaddr < totalStart) + if (p->p_vaddr < totalStart) { totalStart = p->p_vaddr; + firstSegAlign = p->p_align; + } if (p->p_vaddr + p->p_memsz > totalEnd) totalEnd = p->p_vaddr + p->p_memsz; } @@ -612,6 +614,16 @@ u32 ElfReader::GetTotalTextSize() const { return total; } +u32 ElfReader::GetTotalTextSizeFromSeg() const { + u32 total = 0; + for (int i = 0; i < GetNumSegments(); ++i) { + if ((segments[i].p_flags & PF_X) != 0) { + total += segments[i].p_filesz; + } + } + return total; +} + u32 ElfReader::GetTotalDataSize() const { u32 total = 0; for (int i = 0; i < GetNumSections(); ++i) { diff --git a/Core/ELF/ElfReader.h b/Core/ELF/ElfReader.h index 44d33a8e8e..0284b56bd4 100644 --- a/Core/ELF/ElfReader.h +++ b/Core/ELF/ElfReader.h @@ -114,6 +114,10 @@ public: return segments[segment].p_memsz; } + u32 GetFirstSegmentAlign() const { + return firstSegAlign; + } + bool DidRelocate() const { return bRelocate; } @@ -127,6 +131,7 @@ public: } u32 GetTotalTextSize() const; + u32 GetTotalTextSizeFromSeg() const; u32 GetTotalDataSize() const; u32 GetTotalSectionSizeByPrefix(const std::string &prefix) const; @@ -151,4 +156,5 @@ private: u32 vaddr = 0; u32 segmentVAddr[32]; size_t size_ = 0; + u32 firstSegAlign = 0; }; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 34fafd6f4a..4dac67c609 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -21,6 +21,10 @@ // This is a direct port of Coldbird's code from http://code.google.com/p/aemu/ // All credit goes to him! +#if defined(_WIN32) +#include "Common/CommonWindows.h" +#endif + #if !defined(_WIN32) #include #include @@ -62,6 +66,7 @@ SceNetAdhocctlPeerInfo * friends = NULL; SceNetAdhocctlScanInfo * networks = NULL; SceNetAdhocctlScanInfo * newnetworks = NULL; u64 adhocctlStartTime = 0; +bool isAdhocctlBusy = false; int adhocctlState = ADHOCCTL_STATE_DISCONNECTED; int adhocctlCurrentMode = ADHOCCTL_MODE_NONE; int adhocConnectionType = ADHOC_CONNECT; @@ -315,7 +320,7 @@ void changeBlockingMode(int fd, int nonblocking) { #endif } -int countAvailableNetworks() { +int countAvailableNetworks(const bool excludeSelf) { // Network Count int count = 0; @@ -323,7 +328,7 @@ int countAvailableNetworks() { SceNetAdhocctlScanInfo * group = networks; // Count Groups - for (; group != NULL; group = group->next) count++; + for (; group != NULL && (!excludeSelf || !isLocalMAC(&group->bssid.mac_addr)); group = group->next) count++; // Return Network Count return count; @@ -983,8 +988,8 @@ void handleTimeout(SceNetAdhocMatchingContext * context) SceNetAdhocMatchingMemberInternal * next = peer->next; u64_le now = CoreTiming::GetGlobalTimeUsScaled(); //time_now_d()*1000000.0 - // Timeout! - if (peer->state != 0 && (now - peer->lastping) > context->timeout) + // Timeout! Apparently the latest GetGlobalTimeUsScaled (ie. now) have a possibility to be smaller than previous GetGlobalTimeUsScaled (ie. lastping) thus resulting a negative number when subtracted :( + if (peer->state != 0 && static_cast(now - peer->lastping) > static_cast(context->timeout)) { // Spawn Timeout Event if ((context->mode == PSP_ADHOC_MATCHING_MODE_CHILD && peer->state == PSP_ADHOC_MATCHING_PEER_PARENT) || @@ -993,7 +998,7 @@ void handleTimeout(SceNetAdhocMatchingContext * context) // FIXME: TIMEOUT event should only be triggered on Parent/P2P mode and for Parent/P2P peer? spawnLocalEvent(context, PSP_ADHOC_MATCHING_EVENT_TIMEOUT, &peer->mac, 0, NULL); - INFO_LOG(SCENET, "TimedOut Member Peer %s (%lldms)", mac2str(&peer->mac).c_str(), (context->timeout / 1000)); + INFO_LOG(SCENET, "TimedOut Member Peer %s (%lld - %lld = %lld > %lld us)", mac2str(&peer->mac).c_str(), now, peer->lastping, (now - peer->lastping), context->timeout); if (context->mode == PSP_ADHOC_MATCHING_MODE_PARENT) sendDeathMessage(context, peer); @@ -1165,6 +1170,7 @@ void AfterAdhocMipsCall::run(MipsCall& call) { u32 v0 = currentMIPS->r[MIPS_REG_V0]; if (__IsInInterrupt()) ERROR_LOG(SCENET, "AfterAdhocMipsCall::run [ID=%i][Event=%d] is Returning Inside an Interrupt!", HandlerID, EventID); SetAdhocctlInCallback(false); + isAdhocctlBusy = false; DEBUG_LOG(SCENET, "AfterAdhocMipsCall::run [ID=%i][Event=%d] [cbId: %u][retV0: %08x]", HandlerID, EventID, call.cbId, v0); //call.setReturnValue(v0); } @@ -1334,6 +1340,10 @@ int friendFinder(){ if (initNetwork(&product_code) == 0) { networkInited = true; INFO_LOG(SCENET, "FriendFinder: Network [RE]Initialized"); + // At this point we are most-likely not in a Group within the Adhoc Server, so we should probably reset AdhocctlState + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + netAdhocGameModeEntered = false; + isAdhocctlBusy = false; } else { networkInited = false; @@ -1348,7 +1358,7 @@ int friendFinder(){ // Ping Server now = time_now_d() * 1000000.0; // Use time_now_d()*1000000.0 instead of CoreTiming::GetGlobalTimeUsScaled() if the game gets disconnected from AdhocServer too soon when FPS wasn't stable // original code : ((sceKernelGetSystemTimeWide() - lastping) >= ADHOCCTL_PING_TIMEOUT) - if (now - lastping >= PSP_ADHOCCTL_PING_TIMEOUT) { // We may need to use lower interval to prevent getting timeout at Pro Adhoc Server through internet + if (static_cast(now - lastping) >= PSP_ADHOCCTL_PING_TIMEOUT) { // We may need to use lower interval to prevent getting timeout at Pro Adhoc Server through internet // Prepare Packet uint8_t opcode = OPCODE_PING; @@ -1372,7 +1382,7 @@ int friendFinder(){ else { // Update Ping Time lastping = now; - DEBUG_LOG(SCENET, "FriendFinder: Sending OPCODE_PING (%llu)", static_cast(now)); + VERBOSE_LOG(SCENET, "FriendFinder: Sending OPCODE_PING (%llu)", static_cast(now)); } } } @@ -1395,6 +1405,12 @@ int friendFinder(){ } } + // Calculate EnterGameMode Timeout to prevent waiting forever for disconnected players + if (isAdhocctlBusy && adhocctlState == ADHOCCTL_STATE_DISCONNECTED && adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE && netAdhocGameModeEntered && static_cast(now - adhocctlStartTime) > netAdhocEnterGameModeTimeout) { + netAdhocGameModeEntered = false; + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOC_TIMEOUT); + } + // Handle Packets if (rxpos > 0) { // BSSID Packet @@ -1419,14 +1435,14 @@ int friendFinder(){ }) == gameModeMacs.end()) { // Arrange the order to be consistent on all players (Host on top), Starting from our self the rest of new players will be added to the back gameModeMacs.push_back(localMac); + + // FIXME: OPCODE_CONNECT_BSSID only triggered once, but the timing of ADHOCCTL_EVENT_GAME notification could be too soon, since there could be more players that need to join before the event should be notified + if (netAdhocGameModeEntered && gameModeMacs.size() >= requiredGameModeMacs.size()) { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); + } } else WARN_LOG(SCENET, "GameMode SelfMember [%s] Already Existed!", mac2str(&localMac).c_str()); - - if (gameModeMacs.size() >= requiredGameModeMacs.size()) { - //adhocctlState = ADHOCCTL_STATE_GAMEMODE; - notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); - } } else { //adhocctlState = ADHOCCTL_STATE_CONNECTED; @@ -1521,17 +1537,16 @@ int friendFinder(){ it = gameModeMacs.begin() + 1; gameModeMacs.insert(it, packet->mac); } + + // From JPCSP: Join complete when all the required MACs have joined + if (netAdhocGameModeEntered && requiredGameModeMacs.size() > 0 && gameModeMacs.size() == requiredGameModeMacs.size()) { + // TODO: Should we replace gameModeMacs contents with requiredGameModeMacs contents to make sure they are in the same order with macs from sceNetAdhocctlCreateEnterGameMode? But may not be consistent with the list on client side! + //gameModeMacs = requiredGameModeMacs; + notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); + } } else WARN_LOG(SCENET, "GameMode Member [%s] Already Existed!", mac2str(&packet->mac).c_str()); - - // From JPCSP: Join complete when all the required MACs have joined - if (requiredGameModeMacs.size() > 0 && gameModeMacs.size() >= requiredGameModeMacs.size()) { - // TODO: Should we replace gameModeMacs contents with requiredGameModeMacs contents to make sure they are in the same order with macs from sceNetAdhocctlCreateEnterGameMode? But may not be consistent with the list on client side! - //gameModeMacs = requiredGameModeMacs; - //adhocctlState = ADHOCCTL_STATE_GAMEMODE; - notifyAdhocctlHandlers(ADHOCCTL_EVENT_GAME, 0); - } } // Update HUD User Count @@ -1572,13 +1587,14 @@ int friendFinder(){ // Log Incoming Peer Delete Request INFO_LOG(SCENET, "FriendFinder: Incoming Peer Data Delete Request..."); - /*if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { + if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { auto peer = findFriendByIP(packet->ip); - gameModeMacs.erase(std::remove_if(gameModeMacs.begin(), gameModeMacs.end(), - [peer](SceNetEtherAddr const& e) { - return isMacMatch(&e, &peer->mac_addr); - }), gameModeMacs.end()); - }*/ + for (auto& gma : replicaGameModeAreas) + if (isMacMatch(&gma.mac, &peer->mac_addr)) { + gma.updateTimestamp = 0; + break; + } + } // Delete User by IP, should delete by MAC since IP can be shared (behind NAT) isn't? deleteFriendByIP(packet->ip); @@ -1633,7 +1649,8 @@ int friendFinder(){ // Set group parameters // Since 0 is not a valid active channel we fake the channel for Automatic Channel (JPCSP use 11 as default). Ridge Racer 2 will ignore any groups with channel 0 or that doesn't matched with channel value returned from sceUtilityGetSystemParamInt (which mean sceUtilityGetSystemParamInt must not return channel 0 when connected to a network?) group->channel = parameter.channel; //(parameter.channel == PSP_SYSTEMPARAM_ADHOC_CHANNEL_AUTOMATIC) ? defaultWlanChannel : parameter.channel; - group->mode = adhocctlCurrentMode; + // This Mode should be a valid mode (>=0), probably should be sent by AdhocServer since there are 2 possibilities (Normal and GameMode). Air Conflicts - Aces Of World War 2 (which use GameMode) seems to relies on this Mode value. + group->mode = std::max(ADHOCCTL_MODE_NORMAL, adhocctlCurrentMode); // default to ADHOCCTL_MODE_NORMAL // Link into Group List newnetworks = group; @@ -1901,6 +1918,11 @@ int setSockBufferSize(int sock, int opt, int size) { // opt = SO_RCVBUF/SO_SNDBU return setsockopt(sock, SOL_SOCKET, opt, (char *)&n, sizeof(n)); } +int setSockMSS(int sock, int size) { + int mss = size; // 1460; + return setsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, (char*)&mss, sizeof(mss)); +} + int setSockTimeout(int sock, int opt, unsigned long timeout_usec) { // opt = SO_SNDTIMEO/SO_RCVTIMEO if (timeout_usec > 0 && timeout_usec < minSocketTimeoutUS) timeout_usec = minSocketTimeoutUS; // Override timeout for high latency multiplayer #if defined(_WIN32) @@ -1923,8 +1945,21 @@ int getSockNoDelay(int tcpsock) { return opt; } +//#define TCP_QUICKACK 0x0c int setSockNoDelay(int tcpsock, int flag) { int opt = flag; + // Disable ACK Delay when supported +#if defined(TCP_QUICKACK) + setsockopt(tcpsock, IPPROTO_TCP, TCP_QUICKACK, (char*)&opt, sizeof(opt)); +#elif defined(_WIN32) +#if !defined(SIO_TCP_SET_ACK_FREQUENCY) + #define SIO_TCP_SET_ACK_FREQUENCY _WSAIOW(IOC_VENDOR,23) +#endif + int freq = flag? 1:2; // can be 1..255, default is 2 (delayed 200ms) + DWORD retbytes = 0; + WSAIoctl(tcpsock, SIO_TCP_SET_ACK_FREQUENCY, &freq, sizeof(freq), NULL, 0, &retbytes, NULL, NULL); +#endif + // Disable Nagle Algo return setsockopt(tcpsock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt)); } @@ -1950,6 +1985,15 @@ int setSockReuseAddrPort(int sock) { #if !defined(TCP_KEEPIDLE) #define TCP_KEEPIDLE TCP_KEEPALIVE //TCP_KEEPIDLE on Linux is equivalent to TCP_KEEPALIVE on macOS #endif +// VS 2017 compatibility +#if _MSC_VER +#ifndef TCP_KEEPCNT +#define TCP_KEEPCNT 16 +#endif +#ifndef TCP_KEEPINTVL +#define TCP_KEEPINTVL 17 +#endif +#endif int setSockKeepAlive(int sock, bool keepalive, const int keepinvl, const int keepcnt, const int keepidle) { int optval = keepalive ? 1 : 0; int optlen = sizeof(optval); @@ -2064,7 +2108,7 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ parameter.channel = g_Config.iWlanAdhocChannel; // Assign a Valid Channel when connected to AP/Adhoc if it's Auto. JPCSP use 11 as default for Auto (Commonly for Auto: 1, 6, 11) if (parameter.channel == PSP_SYSTEMPARAM_ADHOC_CHANNEL_AUTOMATIC) parameter.channel = defaultWlanChannel; // Faked Active channel to default channel - getLocalMac(¶meter.bssid.mac_addr); + //getLocalMac(¶meter.bssid.mac_addr); // Default ProductId product_code.type = adhoc_id->type; @@ -2074,6 +2118,10 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ if (g_adhocServerIP.in.sin_addr.s_addr == INADDR_NONE) return -1; + // Don't need to connect if AdhocServer IP is the same with this instance localhost IP and having AdhocServer disabled + if (g_adhocServerIP.in.sin_addr.s_addr == g_localhostIP.in.sin_addr.s_addr && !g_Config.bEnableAdhocServer) + return -1; + // Connect to Adhoc Server int errorcode = 0; int cnt = 0; diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 94874334e0..ca0ce7aba2 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -59,6 +59,7 @@ #undef ESHUTDOWN #undef ECONNABORTED #undef ECONNRESET +#undef ECONNREFUSED #undef ENOTCONN #undef EAGAIN #undef EINPROGRESS @@ -69,6 +70,7 @@ #define ESHUTDOWN WSAESHUTDOWN #define ECONNABORTED WSAECONNABORTED #define ECONNRESET WSAECONNRESET +#define ECONNREFUSED WSAECONNREFUSED #define ENOTCONN WSAENOTCONN #define EAGAIN WSAEWOULDBLOCK #define EINPROGRESS WSAEWOULDBLOCK @@ -110,15 +112,22 @@ inline bool isDisconnected(int errcode) { return (errcode == EPIPE || errcode == // Default GameMode definitions #define ADHOC_GAMEMODE_PORT 31000 -#define GAMEMODE_UPDATE_INTERVAL 10000 // 12000 usec on JPCSP, but 10000 works better on BattleZone (in order to get full speed 60 FPS) +#define GAMEMODE_UPDATE_INTERVAL 500 // 12000 usec on JPCSP, but lower value works better on BattleZone (in order to get full speed 60 FPS) +#define GAMEMODE_INIT_DELAY 10000 +#define GAMEMODE_SYNC_TIMEOUT 250000 + +// GameMode Type +#define ADHOCCTL_GAMETYPE_1A 1 +#define ADHOCCTL_GAMETYPE_1B 2 +#define ADHOCCTL_GAMETYPE_2A 3 // psp strutcs and definitions -#define ADHOCCTL_MODE_NONE -1 +#define ADHOCCTL_MODE_NONE -1 // We only use this internally as initial value before attempting to create/connect/join/scan any group #define ADHOCCTL_MODE_NORMAL 0 // ADHOCCTL_MODE_ADHOC #define ADHOCCTL_MODE_GAMEMODE 1 // Event Types for Event Handler -#define ADHOCCTL_EVENT_ERROR 0 +#define ADHOCCTL_EVENT_ERROR 0 // Used to pass error code to Adhocctl Handler? #define ADHOCCTL_EVENT_CONNECT 1 #define ADHOCCTL_EVENT_DISCONNECT 2 #define ADHOCCTL_EVENT_SCAN 3 @@ -138,7 +147,7 @@ inline bool isDisconnected(int errcode) { return (errcode == EPIPE || errcode == // ProductType ( extracted from SSID along with ProductId & GroupName, Pattern = "PSP_([AXS])(.........)_([LG])_(.*)" ) #define PSP_ADHOCCTL_TYPE_COMMERCIAL 0 #define PSP_ADHOCCTL_TYPE_DEBUG 1 -#define PSP_ADHOCCTL_TYPE_SYSTEM 2 +#define PSP_ADHOCCTL_TYPE_SYSTEM 2 // Used for GameSharing? // Kernel Utility Netconf Adhoc Types #define UTILITY_NETCONF_TYPE_CONNECT_ADHOC 2 @@ -318,6 +327,7 @@ typedef struct GameModeArea { //int socket; // PDP socket? u64 updateTimestamp; int dataUpdated; + int dataSent; SceNetEtherAddr mac; u8* data; // upto "size" bytes started from "addr" ? } PACK GameModeArea; @@ -390,8 +400,8 @@ typedef struct SceNetAdhocGameModeBufferStat { // Adhoc ID (Game Product Key) #define ADHOCCTL_ADHOCID_LEN 9 typedef struct SceNetAdhocctlAdhocId { - s32_le type; - uint8_t data[ADHOCCTL_ADHOCID_LEN]; + s32_le type; // Air Conflicts - Aces Of World War 2 is using 2 for GameSharing? + uint8_t data[ADHOCCTL_ADHOCID_LEN]; // Air Conflicts - Aces Of World War 2 is using "000000001" for GameSharing? uint8_t padding[3]; } PACK SceNetAdhocctlAdhocId; // should this be packed? #ifdef _MSC_VER @@ -557,19 +567,24 @@ typedef struct SceNetAdhocMatchingContext { // End of psp definitions enum { + // pspnet_adhoc_auth + ERROR_NET_ADHOC_AUTH_ALREADY_INITIALIZED = 0x80410601, + + // pspnet_adhoc ERROR_NET_ADHOC_INVALID_SOCKET_ID = 0x80410701, ERROR_NET_ADHOC_INVALID_ADDR = 0x80410702, ERROR_NET_ADHOC_INVALID_PORT = 0x80410703, + ERROR_NET_ADHOC_INVALID_BUFLEN = 0x80410704, ERROR_NET_ADHOC_INVALID_DATALEN = 0x80410705, - ERROR_NET_ADHOC_NOT_ENOUGH_SPACE = 0x80400706, + ERROR_NET_ADHOC_NOT_ENOUGH_SPACE = 0x80400706, // not a typo ERROR_NET_ADHOC_SOCKET_DELETED = 0x80410707, ERROR_NET_ADHOC_SOCKET_ALERTED = 0x80410708, ERROR_NET_ADHOC_WOULD_BLOCK = 0x80410709, //ERROR_NET_ADHOC_NO_DATA_AVAILABLE ERROR_NET_ADHOC_PORT_IN_USE = 0x8041070a, ERROR_NET_ADHOC_NOT_CONNECTED = 0x8041070B, ERROR_NET_ADHOC_DISCONNECTED = 0x8041070c, - ERROR_NET_ADHOC_NOT_OPENED = 0x8040070D, - ERROR_NET_ADHOC_NOT_LISTENED = 0x8040070E, + ERROR_NET_ADHOC_NOT_OPENED = 0x8040070D, // not a typo + ERROR_NET_ADHOC_NOT_LISTENED = 0x8040070E, // not a typo ERROR_NET_ADHOC_SOCKET_ID_NOT_AVAIL = 0x8041070F, ERROR_NET_ADHOC_PORT_NOT_AVAIL = 0x80410710, ERROR_NET_ADHOC_INVALID_ARG = 0x80410711, @@ -585,7 +600,9 @@ enum { ERROR_NET_ADHOC_NOT_IN_GAMEMODE = 0x8041071B, ERROR_NET_ADHOC_NOT_CREATED = 0x8041071C, + // pspnet_adhoc_matching ERROR_NET_ADHOC_MATCHING_INVALID_MODE = 0x80410801, + ERROR_NET_ADHOC_MATCHING_INVALID_PORT = 0x80410802, ERROR_NET_ADHOC_MATCHING_INVALID_MAXNUM = 0x80410803, ERROR_NET_ADHOC_MATCHING_RXBUF_TOO_SHORT = 0x80410804, ERROR_NET_ADHOC_MATCHING_INVALID_OPTLEN = 0x80410805, @@ -609,21 +626,26 @@ enum { ERROR_NET_ADHOC_MATCHING_NOT_ESTABLISHED = 0x80410817, ERROR_NET_ADHOC_MATCHING_DATA_BUSY = 0x80410818, + // pspnet_adhocctl + ERROR_NET_ADHOCCTL_NOT_LEFT_IBSS = 0x80410b01, + ERROR_NET_ADHOCCTL_ALREADY_CONNECTED = 0x80410b02, ERROR_NET_ADHOCCTL_WLAN_SWITCH_OFF = 0x80410b03, ERROR_NET_ADHOCCTL_INVALID_ARG = 0x80410B04, + ERROR_NET_ADHOCCTL_TIMEOUT = 0x80410b05, ERROR_NET_ADHOCCTL_ID_NOT_FOUND = 0x80410B06, ERROR_NET_ADHOCCTL_ALREADY_INITIALIZED = 0x80410b07, ERROR_NET_ADHOCCTL_NOT_INITIALIZED = 0x80410b08, ERROR_NET_ADHOCCTL_DISCONNECTED = 0x80410b09, + ERROR_NET_ADHOCCTL_NO_SCAN_INFO = 0x80410b0a, + ERROR_NET_ADHOCCTL_INVALID_IBSS = 0x80410b0b, ERROR_NET_ADHOCCTL_NOT_ENTER_GAMEMODE = 0x80410B0C, ERROR_NET_ADHOCCTL_CHANNEL_NOT_AVAILABLE = 0x80410B0D, + ERROR_NET_ADHOCCTL_WLAN_BEACON_LOST = 0x80410b0e, + ERROR_NET_ADHOCCTL_WLAN_SUSPENDED = 0x80410b0f, ERROR_NET_ADHOCCTL_BUSY = 0x80410b10, + ERROR_NET_ADHOCCTL_CHANNEL_NOT_MATCH = 0x80410b11, ERROR_NET_ADHOCCTL_TOO_MANY_HANDLERS = 0x80410b12, ERROR_NET_ADHOCCTL_STACKSIZE_TOO_SHORT = 0x80410B13, - - ERROR_NET_WLAN_INVALID_ARG = 0x80410D13, - - ERROR_NET_NO_SPACE = 0x80410001 }; const size_t MAX_ADHOCCTL_HANDLERS = 32; //4 @@ -902,6 +924,7 @@ extern bool friendFinderRunning; extern SceNetAdhocctlPeerInfo * friends; extern SceNetAdhocctlScanInfo * networks; extern u64 adhocctlStartTime; +extern bool isAdhocctlBusy; extern int adhocctlState; extern int adhocctlCurrentMode; extern int adhocConnectionType; @@ -1014,7 +1037,7 @@ void changeBlockingMode(int fd, int nonblocking); * Count Virtual Networks by analyzing the Friend List * @return Number of Virtual Networks */ -int countAvailableNetworks(); +int countAvailableNetworks(const bool excludeSelf = false); /* * Find an existing group in networks @@ -1268,6 +1291,11 @@ int getSockBufferSize(int sock, int opt); */ int setSockBufferSize(int sock, int opt, int size); +/* +* Set TCP Socket Maximum Segment Size (default is 1460 on 1500 MTU) +*/ +int setSockMSS(int sock, int size); + /* * Set Socket TimeOut (opt = SO_SNDTIMEO/SO_RCVTIMEO) */ diff --git a/Core/HLE/proAdhocServer.cpp b/Core/HLE/proAdhocServer.cpp index 4a2750f20f..15757a668e 100644 --- a/Core/HLE/proAdhocServer.cpp +++ b/Core/HLE/proAdhocServer.cpp @@ -55,6 +55,7 @@ #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" #include "Core/Util/PortManager.h" +#include "Core/Instance.h" #include "Core/Core.h" #include "Core/Host.h" #include "Core/HLE/proAdhocServer.h" @@ -1823,6 +1824,9 @@ int create_listen_socket(uint16_t port) // Created Socket if(fd != -1) { + // Ignore SIGPIPE when supported (ie. BSD/MacOS) + setSockNoSIGPIPE(fd, 1); + // Enable KeepAlive enable_keepalive(fd); @@ -1842,12 +1846,10 @@ int create_listen_socket(uint16_t port) local.sin_addr.s_addr = INADDR_ANY; local.sin_port = htons(port); - //Should only bind to specific IP for the 2nd or more instance of PPSSPP to prevent communication interference issue when sharing the same port. Doesn't work well when PPSSPP_ID reseted everytime emulation restarted. - /* + // Should only bind to specific IP for the 2nd or more instance of PPSSPP to prevent communication interference issue when sharing the same port. (ie. Capcom Classics Collection Remixed) if (PPSSPP_ID > 1) { local.sin_addr = g_localhostIP.in.sin_addr; } - */ // Bind Local Address to Socket int bindresult = bind(fd, (struct sockaddr *)&local, sizeof(local)); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 4eb3f848e5..e21cd210a3 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -670,11 +670,12 @@ static void DoFrameIdleTiming() { // Give a little extra wiggle room in case the next vblank does more work. const double goal = lastFrameTime + (numVBlanksSinceFlip - 1) * scaledVblank - 0.001; if (numVBlanksSinceFlip >= 2 && before < goal) { - while (time_now_d() < goal) { + double cur_time; + while ((cur_time = time_now_d()) < goal) { #ifdef _WIN32 sleep_ms(1); #else - const double left = goal - time_now_d(); + const double left = goal - cur_time; usleep((long)(left * 1000000)); #endif } @@ -893,9 +894,10 @@ void hleLagSync(u64 userdata, int cyclesLate) { // Don't lag too long ever, if they leave it paused. double now = before; while (now < goal && goal < now + 0.01) { + // Tight loop on win32 - intentionally, as timing is otherwise not precise enough. #ifndef _WIN32 - const double left = goal - time_now_d(); - usleep((long)(left * 1000000)); + const double left = goal - now; + usleep((long)(left * 1000000.0)); #endif now = time_now_d(); } diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index a12ba039f9..43e0700b98 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -621,8 +621,6 @@ static void __IoVblank() { } void __IoInit() { - MemoryStick_Init(); - asyncNotifyEvent = CoreTiming::RegisterEvent("IoAsyncNotify", __IoAsyncNotify); syncNotifyEvent = CoreTiming::RegisterEvent("IoSyncNotify", __IoSyncNotify); @@ -663,6 +661,7 @@ void __IoInit() { __KernelRegisterWaitTypeFuncs(WAITTYPE_ASYNCIO, __IoAsyncBeginCallback, __IoAsyncEndCallback); + MemoryStick_Init(); lastMemStickState = MemoryStick_State(); lastMemStickFatState = MemoryStick_FatState(); __DisplayListenVblank(__IoVblank); diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 8ceb699fad..50b0b75fdf 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -1141,6 +1141,16 @@ static int sceKernelPrintf(const char *formatString) supported = false; } + // Scrub for beeps and other suspicious control characters. + for (size_t i = 0; i < result.size(); i++) { + switch (result[i]) { + case 7: // BEL + case 8: // Backspace + result[i] = ' '; + break; + } + } + // Just in case there were embedded strings that had \n's. if (!result.empty() && result[result.size() - 1] == '\n') result.resize(result.size() - 1); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 4952f9be31..a59c545bab 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -168,6 +168,7 @@ struct NativeModule { char name[28]; u32_le status; u32_le unk1; + u32_le modid; // 0x2C u32_le usermod_thid; u32_le memid; u32_le mpidtext; @@ -233,7 +234,6 @@ enum NativeModuleStatus { class PSPModule : public KernelObject { public: - PSPModule() : textStart(0), textEnd(0), libstub(0), libstubend(0), memoryBlockAddr(0), isFake(false) {} ~PSPModule() { if (memoryBlockAddr) { // If it's either below user memory, or using a high kernel bit, it's in kernel. @@ -244,6 +244,11 @@ public: } g_symbolMap->UnloadModule(memoryBlockAddr, memoryBlockSize); } + + if (modulePtr) { + //Only alloc at kernel memory. + kernelMemory.Free(modulePtr); + } } const char *GetName() override { return nm.name; } const char *GetTypeName() override { return GetStaticTypeName(); } @@ -263,11 +268,22 @@ public: void DoState(PointerWrap &p) override { - auto s = p.Section("Module", 1, 4); + auto s = p.Section("Module", 1, 5); if (!s) return; - Do(p, nm); + if (s >= 5) { + Do(p, nm); + } else { + char temp[192]; + NativeModule *pnm = &nm; + char *ptemp = temp; + DoArray(p, ptemp, 0xC0); + memcpy(pnm, ptemp, 0x2C); + pnm->modid = GetUID(); + memcpy(((uint8_t *)pnm) + 0x30, ((uint8_t *)ptemp) + 0x2C, 0xC0 - 0x2C); + } + Do(p, memoryBlockAddr); Do(p, memoryBlockSize); Do(p, isFake); @@ -290,6 +306,10 @@ public: Do(p, libstubend); } + if (s >= 5) { + Do(p, modulePtr); + } + ModuleWaitingThread mwt = {0}; Do(p, waitingThreads, mwt); FuncSymbolExport fsx = {{0}}; @@ -402,7 +422,7 @@ public: return impExpModuleNames.find(moduleName) != impExpModuleNames.end(); } - NativeModule nm; + NativeModule nm{}; std::vector waitingThreads; std::vector exportedFuncs; @@ -410,19 +430,19 @@ public: std::vector exportedVars; std::vector importedVars; std::set impExpModuleNames; - // Keep track of the code region so we can throw out analysis results // when unloaded. - u32 textStart; - u32 textEnd; + u32 textStart = 0; + u32 textEnd = 0; // Keep track of the libstub pointers so we can recheck on load state. - u32 libstub; - u32 libstubend; + u32 libstub = 0; + u32 libstubend = 0; - u32 memoryBlockAddr; - u32 memoryBlockSize; - bool isFake; + u32 memoryBlockAddr = 0; + u32 memoryBlockSize = 0; + u32 modulePtr = 0; + bool isFake = false; }; KernelObject *__KernelModuleObject() @@ -1104,6 +1124,8 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load loadedModules.insert(module->GetUID()); memset(&module->nm, 0, sizeof(module->nm)); + module->nm.modid = module->GetUID(); + bool reportedModule = false; u32 devkitVersion = 0; u8 *newptr = 0; @@ -1310,6 +1332,12 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load if (textSection == -1) { module->textStart = reader.GetVaddr(); module->textEnd = firstImportStubAddr - 4; + // Reference Jpcsp. + if (reader.GetFirstSegmentAlign() > 0) + module->textStart &= ~(reader.GetFirstSegmentAlign() - 1); + // PSP set these values even if no section. + module->nm.text_addr = module->textStart; + module->nm.text_size = reader.GetTotalTextSizeFromSeg(); } if (!module->isFake) { @@ -1540,6 +1568,15 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load } } + u32 moduleSize = sizeof(module->nm); + char tag[32]; + snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid); + module->modulePtr = kernelMemory.Alloc(moduleSize, true, tag); + + // Fill the struct. + if (Memory::IsValidAddress(module->modulePtr)) + Memory::WriteStruct(module->modulePtr, &module->nm); + error = 0; return module; } @@ -1896,6 +1933,15 @@ u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) { module->nm.entry_addr = -1; module->nm.gp_value = -1; + u32 moduleSize = sizeof(module->nm); + char tag[32]; + snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid); + module->modulePtr = kernelMemory.Alloc(moduleSize, true, tag); + + // Fill the struct. + if(Memory::IsValidAddress(module->modulePtr)) + Memory::WriteStruct(module->modulePtr, &module->nm); + // TODO: It would be more ideal to allocate memory for this module. return hleLogSuccessInfoI(LOADER, module->GetUID(), "created fake module"); @@ -2313,7 +2359,7 @@ void __KernelReturnFromModuleFunc() { if (module->nm.status == MODULE_STATUS_UNLOADING) { // TODO: Maybe should maintain the exitCode? - sceKernelDeleteThread(it->threadID); + sceKernelTerminateDeleteThread(it->threadID); } else { if (it->statusPtr != 0) Memory::Write_U32(exitStatus, it->statusPtr); @@ -2367,16 +2413,30 @@ static u32 sceKernelGetModuleId() u32 sceKernelFindModuleByUID(u32 uid) { - ERROR_LOG(SCEMODULE, "UNIMPL sceKernelFindModuleByUID(%d)", uid); - return 0; + u32 error; + PSPModule *module = kernelObjects.Get(uid, error); + if (!module || module->isFake) { + ERROR_LOG(SCEMODULE, "0 = sceKernelFindModuleByUID(%d): Module Not Found or Fake", uid); + return 0; + } + INFO_LOG(SCEMODULE, "%d = sceKernelFindModuleByUID(%d)", module->modulePtr, uid); + return module->modulePtr; } u32 sceKernelFindModuleByName(const char *name) { - int index = GetModuleIndex(name); - u32 temp = index + 1; - INFO_LOG(SCEMODULE, "%d = sceKernelFindModuleByName(%s)", temp, name); - return temp; + u32 error; + for (SceUID moduleId : loadedModules) { + PSPModule *module = kernelObjects.Get(moduleId, error); + if (!module) + continue; + if (!module->isFake && strcmp(name, module->nm.name) == 0) { + INFO_LOG(SCEMODULE, "%d = sceKernelFindModuleByName(%s)", module->modulePtr, name); + return module->modulePtr; + } + } + WARN_LOG(SCEMODULE, "0 = sceKernelFindModuleByName(%s): Module Not Found or Fake", name); + return 0; } static u32 sceKernelLoadModuleByID(u32 id, u32 flags, u32 lmoptionPtr) diff --git a/Core/HLE/sceKernelTime.cpp b/Core/HLE/sceKernelTime.cpp index 8bfa812e30..67fe49189f 100644 --- a/Core/HLE/sceKernelTime.cpp +++ b/Core/HLE/sceKernelTime.cpp @@ -32,6 +32,7 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceRtc.h" #include "Core/MemMap.h" +#include "Core/System.h" #include "StringUtils.h" // The time when the game started. @@ -40,6 +41,14 @@ static time_t start_time; void __KernelTimeInit() { time(&start_time); + if (PSP_CoreParameter().compat.flags().DateLimited) { + // Car Jack Streets(NPUZ00043) requires that the date cannot exceed a certain time. + // 2011 year makes it work fine. + tm *tm; + tm = localtime(&start_time); + tm->tm_year = 111;// 2011 year. + start_time = mktime(tm); + } } void __KernelTimeDoState(PointerWrap &p) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 6a10183fab..67d71a69e7 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -98,6 +98,10 @@ static std::list pmp_queue; //list of pmp video frames have been decod static std::list pmp_ContextList; //list of pmp media contexts static bool pmp_oldStateLoaded = false; // for dostate +// Calculate the number of total packets added to the ringbuffer by calling the sceMpegRingbufferPut() once. +static int ringbufferPutPacketsAdded = 0; +static bool useRingbufferPutCallbackMulti = true; + #ifdef USE_FFMPEG extern "C" { @@ -387,7 +391,7 @@ void __MpegInit() { } void __MpegDoState(PointerWrap &p) { - auto s = p.Section("sceMpeg", 1, 2); + auto s = p.Section("sceMpeg", 1, 3); if (!s) return; @@ -400,6 +404,12 @@ void __MpegDoState(PointerWrap &p) { // Let's assume the oldest version. mpegLibVersion = 0x0101; } else { + if (s < 3) { + useRingbufferPutCallbackMulti = false; + ringbufferPutPacketsAdded = 0; + } else { + Do(p, ringbufferPutPacketsAdded); + } Do(p, streamIdGen); Do(p, mpegLibVersion); } @@ -667,6 +677,7 @@ static int sceMpegRegistStream(u32 mpeg, u32 streamType, u32 streamNum) switch (streamType) { case MPEG_AVC_STREAM: ctx->avcRegistered = true; + ctx->mediaengine->addVideoStream(streamNum); // TODO: Probably incorrect? ctx->mediaengine->setVideoStream(streamNum); break; @@ -1437,16 +1448,17 @@ void PostPutAction::run(MipsCall &call) { int writeOffset = ringbuffer->packetsWritePos % (s32)ringbuffer->packets; const u8 *data = Memory::GetPointer(ringbuffer->data + writeOffset * 2048); - int packetsAdded = currentMIPS->r[MIPS_REG_V0]; + int packetsAddedThisRound = currentMIPS->r[MIPS_REG_V0]; + ringbufferPutPacketsAdded += packetsAddedThisRound; // It seems validation is done only by older mpeg libs. - if (mpegLibVersion < 0x0105 && packetsAdded > 0) { + if (mpegLibVersion < 0x0105 && packetsAddedThisRound > 0) { // TODO: Faster / less wasteful validation. - std::unique_ptr demuxer(new MpegDemux(packetsAdded * 2048, 0)); + std::unique_ptr demuxer(new MpegDemux(packetsAddedThisRound * 2048, 0)); int readOffset = ringbuffer->packetsRead % (s32)ringbuffer->packets; const u8 *buf = Memory::GetPointer(ringbuffer->data + readOffset * 2048); bool invalid = false; - for (int i = 0; i < packetsAdded; ++i) { + for (int i = 0; i < packetsAddedThisRound; ++i) { demuxer->addStreamData(buf, 2048); buf += 2048; @@ -1461,40 +1473,43 @@ void PostPutAction::run(MipsCall &call) { if (mpegLibVersion <= 0x0103) { // Act like they were actually added, but don't increment read pos. - ringbuffer->packetsWritePos += packetsAdded; - ringbuffer->packetsAvail += packetsAdded; + ringbuffer->packetsWritePos += packetsAddedThisRound; + ringbuffer->packetsAvail += packetsAddedThisRound; } return; } } - if (ringbuffer->packetsRead == 0 && ctx->mediaengine && packetsAdded > 0) { + if (ringbuffer->packetsRead == 0 && ctx->mediaengine && packetsAddedThisRound > 0) { // init mediaEngine AnalyzeMpeg(ctx->mpegheader, 2048, ctx); ctx->mediaengine->loadStream(ctx->mpegheader, 2048, ringbuffer->packets * ringbuffer->packetSize); } - if (packetsAdded > 0) { - if (packetsAdded > ringbuffer->packets - ringbuffer->packetsAvail) { - WARN_LOG(ME, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAdded, ringbuffer->packets - ringbuffer->packetsAvail); - packetsAdded = ringbuffer->packets - ringbuffer->packetsAvail; + if (packetsAddedThisRound > 0) { + if (packetsAddedThisRound > ringbuffer->packets - ringbuffer->packetsAvail) { + WARN_LOG(ME, "sceMpegRingbufferPut clamping packetsAdded old=%i new=%i", packetsAddedThisRound, ringbuffer->packets - ringbuffer->packetsAvail); + packetsAddedThisRound = ringbuffer->packets - ringbuffer->packetsAvail; } - int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(data, packetsAdded * 2048) / 2048; - if (actuallyAdded != packetsAdded) { + int actuallyAdded = ctx->mediaengine == NULL ? 8 : ctx->mediaengine->addStreamData(data, packetsAddedThisRound * 2048) / 2048; + if (actuallyAdded != packetsAddedThisRound) { WARN_LOG_REPORT(ME, "sceMpegRingbufferPut(): unable to enqueue all added packets, going to overwrite some frames."); } - ringbuffer->packetsRead += packetsAdded; - ringbuffer->packetsWritePos += packetsAdded; - ringbuffer->packetsAvail += packetsAdded; + ringbuffer->packetsRead += packetsAddedThisRound; + ringbuffer->packetsWritePos += packetsAddedThisRound; + ringbuffer->packetsAvail += packetsAddedThisRound; } - DEBUG_LOG(ME, "packetAdded: %i packetsRead: %i packetsTotal: %i", packetsAdded, ringbuffer->packetsRead, ringbuffer->packets); + DEBUG_LOG(ME, "packetAdded: %i packetsRead: %i packetsTotal: %i", packetsAddedThisRound, ringbuffer->packetsRead, ringbuffer->packets); - call.setReturnValue(packetsAdded); + call.setReturnValue(ringbufferPutPacketsAdded); } // Program signals that it has written data to the ringbuffer and gets a callback ? static u32 sceMpegRingbufferPut(u32 ringbufferAddr, int numPackets, int available) { + // Generally, program will call sceMpegRingbufferAvailableSize() before this func. + // Still need to check available? + numPackets = std::min(numPackets, available); if (numPackets <= 0) { DEBUG_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i): no packets to enqueue", ringbufferAddr, numPackets, available); @@ -1513,20 +1528,29 @@ static u32 sceMpegRingbufferPut(u32 ringbufferAddr, int numPackets, int availabl WARN_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i): bad mpeg handle %08x", ringbufferAddr, numPackets, available, ringbuffer->mpeg); return -1; } - + ringbufferPutPacketsAdded = 0; // Execute callback function as a direct MipsCall, no blocking here so no messing around with wait states etc if (ringbuffer->callback_addr != 0) { DEBUG_LOG(ME, "sceMpegRingbufferPut(%08x, %i, %i)", ringbufferAddr, numPackets, available); - PostPutAction *action = (PostPutAction *)__KernelCreateAction(actionPostPut); - action->setRingAddr(ringbufferAddr); - // TODO: Should call this multiple times until we get numPackets. + // Call this multiple times until we get numPackets. // Normally this would be if it did not read enough, but also if available > packets. // Should ultimately return the TOTAL number of returned packets. int writeOffset = ringbuffer->packetsWritePos % (s32)ringbuffer->packets; - u32 packetsThisRound = std::min(numPackets, (s32)ringbuffer->packets - writeOffset); - u32 args[3] = {(u32)ringbuffer->data + (u32)writeOffset * 2048, packetsThisRound, (u32)ringbuffer->callback_args}; - hleEnqueueCall(ringbuffer->callback_addr, 3, args, action); + u32 packetsThisRound = 0; + while (numPackets) { + PostPutAction *action = (PostPutAction *)__KernelCreateAction(actionPostPut); + action->setRingAddr(ringbufferAddr); + + packetsThisRound = std::min(numPackets, (s32)ringbuffer->packets - writeOffset); + numPackets -= packetsThisRound; + u32 args[3] = { (u32)ringbuffer->data + (u32)writeOffset * 2048, packetsThisRound, (u32)ringbuffer->callback_args }; + hleEnqueueCall(ringbuffer->callback_addr, 3, args, action); + writeOffset = (writeOffset + packetsThisRound) % (s32)ringbuffer->packets; + // Old savestate don't use this feature, just for compatibility. + if (!useRingbufferPutCallbackMulti) + break; + } } else { ERROR_LOG_REPORT(ME, "sceMpegRingbufferPut: callback_addr zero"); } @@ -1953,10 +1977,7 @@ static u32 sceMpegAvcCsc(u32 mpeg, u32 sourceAddr, u32 rangeAddr, int frameWidth int y = Memory::Read_U32(rangeAddr + 4); int width = Memory::Read_U32(rangeAddr + 8); int height = Memory::Read_U32(rangeAddr + 12); - if (((x | y | width | height) & 0xF) != 0) { - WARN_LOG(ME, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x) returning ERROR_MPEG_INVALID_VALUE", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); - return ERROR_MPEG_INVALID_VALUE; - } + if (x < 0 || y < 0 || width < 0 || height < 0) { WARN_LOG(ME, "sceMpegAvcCsc(%08x, %08x, %08x, %i, %08x) returning ERROR_INVALID_VALUE", mpeg, sourceAddr, rangeAddr, frameWidth, destAddr); return SCE_KERNEL_ERROR_INVALID_VALUE; diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 2de660cc5e..448b0ba66e 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -135,8 +135,10 @@ static void __ApctlState(u64 userdata, int cyclesLate) { u32 error = 0; SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); - if (waitID == 0 || error != 0) + if (waitID == 0 || error != 0) { + WARN_LOG(SCENET, "sceNetApctl State WaitID(%i) on Thread(%i) already woken up? (error: %d)", uid, threadID, error); return; + } u32 waitVal = __KernelGetWaitValue(threadID, error); if (error == 0) { @@ -693,6 +695,10 @@ static u32 sceWlanGetEtherAddr(u32 addrAddr) { } static u32 sceNetGetLocalEtherAddr(u32 addrAddr) { + // FIXME: Return 0x80410180 (pspnet[_core] error code?) before successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode == ADHOCCTL_MODE_NONE) + if (adhocctlCurrentMode == ADHOCCTL_MODE_NONE) + return hleLogDebug(SCENET, 0x80410180, "address not available?"); + return sceWlanGetEtherAddr(addrAddr); } diff --git a/Core/HLE/sceNet.h b/Core/HLE/sceNet.h index 3ea8b9811d..a1ba8285c3 100644 --- a/Core/HLE/sceNet.h +++ b/Core/HLE/sceNet.h @@ -49,14 +49,80 @@ const size_t MAX_APCTL_HANDLERS = 32; enum { - ERROR_NET_BUFFER_TOO_SMALL = 0x80400706, + // net common errors + ERROR_NET_NO_SPACE = 0x80410001, + ERROR_NET_INTERNAL = 0x80410002, + ERROR_NET_INVALID_ARG = 0x80410003, + ERROR_NET_NO_ENTRY = 0x80410004, + // pspnet_core + ERROR_NET_CORE_NOT_TERMINATED = 0x80410101, + ERROR_NET_CORE_INTERFACE_BUSY = 0x80410102, + ERROR_NET_CORE_INVALID_ARG = 0x80410103, + ERROR_NET_CORE_THREAD_NOT_FOUND = 0x80410104, + ERROR_NET_CORE_THREAD_BUSY = 0x80410105, + ERROR_NET_CORE_80211_NO_BSS = 0x80410106, + ERROR_NET_CORE_80211_NO_AVAIL_BSS = 0x80410107, + + // pspnet_inet ERROR_NET_INET_ALREADY_INITIALIZED = 0x80410201, + ERROR_NET_INET_SOCKET_BUSY = 0x80410202, + ERROR_NET_INET_CONFIG_INVALID_ARG = 0x80410203, + ERROR_NET_INET_GET_IFADDR = 0x80410204, + ERROR_NET_INET_SET_IFADDR = 0x80410205, + ERROR_NET_INET_DEL_IFADDR = 0x80410206, + ERROR_NET_INET_NO_DEFAULT_ROUTE = 0x80410207, + ERROR_NET_INET_GET_ROUTE = 0x80410208, + ERROR_NET_INET_SET_ROUTE = 0x80410209, + ERROR_NET_INET_FLUSH_ROUTE = 0x8041020a, + ERROR_NET_INET_INVALID_ARG = 0x8041020b, - ERROR_NET_RESOLVER_BAD_ID = 0x80410408, + // pspnet_poeclient + ERROR_NET_POECLIENT_INIT = 0x80410301, + ERROR_NET_POECLIENT_NO_PADO = 0x80410302, + ERROR_NET_POECLIENT_NO_PADS = 0x80410303, + ERROR_NET_POECLIENT_GET_PADT = 0x80410304, + ERROR_NET_POECLIENT_SERVICE_NAME = 0x80410305, + ERROR_NET_POECLIENT_AC_SYSTEM = 0x80410306, + ERROR_NET_POECLIENT_GENERIC = 0x80410307, + ERROR_NET_POECLIENT_AUTH = 0x80410308, + ERROR_NET_POECLIENT_NETWORK = 0x80410309, + ERROR_NET_POECLIENT_TERMINATE = 0x8041030a, + ERROR_NET_POECLIENT_NOT_STARTED = 0x8041030b, + + // pspnet_resolver + ERROR_NET_RESOLVER_NOT_TERMINATED = 0x80410401, + ERROR_NET_RESOLVER_NO_DNS_SERVER = 0x80410402, + ERROR_NET_RESOLVER_INVALID_PTR = 0x80410403, + ERROR_NET_RESOLVER_INVALID_BUFLEN = 0x80410404, + ERROR_NET_RESOLVER_INVALID_ID = 0x80410405, + ERROR_NET_RESOLVER_ID_MAX = 0x80410406, + ERROR_NET_RESOLVER_NO_MEM = 0x80410407, + ERROR_NET_RESOLVER_BAD_ID = 0x80410408, // ERROR_NET_RESOLVER_ID_NOT_FOUND + ERROR_NET_RESOLVER_CTX_BUSY = 0x80410409, ERROR_NET_RESOLVER_ALREADY_STOPPED = 0x8041040a, - ERROR_NET_RESOLVER_INVALID_HOST = 0x80410414, + ERROR_NET_RESOLVER_NOT_SUPPORTED = 0x8041040b, + ERROR_NET_RESOLVER_BUF_NO_SPACE = 0x8041040c, + ERROR_NET_RESOLVER_INVALID_PACKET = 0x8041040d, + ERROR_NET_RESOLVER_STOPPED = 0x8041040e, + ERROR_NET_RESOLVER_SOCKET = 0x8041040f, + ERROR_NET_RESOLVER_TIMEOUT = 0x80410410, + ERROR_NET_RESOLVER_NO_RECORD = 0x80410411, + ERROR_NET_RESOLVER_RES_PACKET_FORMAT = 0x80410412, + ERROR_NET_RESOLVER_RES_SERVER_FAILURE = 0x80410413, + ERROR_NET_RESOLVER_INVALID_HOST = 0x80410414, // ERROR_NET_RESOLVER_NO_HOST + ERROR_NET_RESOLVER_RES_NOT_IMPLEMENTED = 0x80410415, + ERROR_NET_RESOLVER_RES_SERVER_REFUSED = 0x80410416, + ERROR_NET_RESOLVER_INTERNAL = 0x80410417, + // pspnet_dhcp + ERROR_NET_DHCP_INVALID_PACKET = 0x80410501, + ERROR_NET_DHCP_NO_SERVER = 0x80410502, + ERROR_NET_DHCP_SENT_DECLINE = 0x80410503, + ERROR_NET_DHCP_LEASE_TIME = 0x80410504, + ERROR_NET_DHCP_GET_NAK = 0x80410505, + + // pspnet_apctl ERROR_NET_APCTL_ALREADY_INITIALIZED = 0x80410a01, ERROR_NET_APCTL_INVALID_CODE = 0x80410a02, ERROR_NET_APCTL_INVALID_IP = 0x80410a03, @@ -68,6 +134,30 @@ enum { ERROR_NET_APCTL_INVALID_ID = 0x80410a09, ERROR_NET_APCTL_WLAN_SUSPENDED = 0x80410a0a, ERROR_NET_APCTL_TIMEOUT = 0x80410a0b, + + // wlan errors + ERROR_NET_WLAN_ALREADY_JOINED = 0x80410d01, + ERROR_NET_WLAN_TRY_JOIN = 0x80410d02, + ERROR_NET_WLAN_SCANNING = 0x80410d03, + ERROR_NET_WLAN_INVALID_PARAMETER = 0x80410d04, + ERROR_NET_WLAN_NOT_SUPPORTED = 0x80410d05, + ERROR_NET_WLAN_NOT_JOIN_BSS = 0x80410d06, + ERROR_NET_WLAN_ASSOC_TIMEOUT = 0x80410d07, + ERROR_NET_WLAN_ASSOC_REFUSED = 0x80410d08, + ERROR_NET_WLAN_ASSOC_FAIL = 0x80410d09, + ERROR_NET_WLAN_DISASSOC_FAIL = 0x80410d0a, + ERROR_NET_WLAN_JOIN_FAIL = 0x80410d0b, + ERROR_NET_WLAN_POWER_OFF = 0x80410d0c, + ERROR_NET_WLAN_INTERNAL_FAIL = 0x80410d0d, + ERROR_NET_WLAN_DEVICE_NOT_READY = 0x80410d0e, + ERROR_NET_WLAN_ALREADY_ATTACHED = 0x80410d0f, + ERROR_NET_WLAN_NOT_SET_WEP = 0x80410d10, + ERROR_NET_WLAN_TIMEOUT = 0x80410d11, + ERROR_NET_WLAN_NO_SPACE = 0x80410d12, + ERROR_NET_WLAN_INVALID_ARG = 0x80410D13, + ERROR_NET_WLAN_NOT_IN_GAMEMODE = 0x80410d14, + ERROR_NET_WLAN_LEAVE_FAIL = 0x80410d15, + ERROR_NET_WLAN_SUSPENDED = 0x80410d16, }; enum { diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index f52b486cb6..21dc1c0ef2 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -65,16 +65,17 @@ bool netAdhocInited; bool netAdhocctlInited; bool networkInited = false; -bool netAdhocGameModeEntered; +bool netAdhocGameModeEntered = false; +int netAdhocEnterGameModeTimeout = 15000000; // 15 sec as default timeout, to wait for all players to join bool netAdhocMatchingInited; int netAdhocMatchingStarted = 0; -int adhocDefaultTimeout = 3000000; //3000000 usec +int adhocDefaultTimeout = 5000000; //2000000 usec // For some unknown reason, sometimes it tooks more than 2 seconds for Adhocctl Init to connect to AdhocServer on localhost (normally only 10 ms), and sometimes it tooks more than 1 seconds for built-in AdhocServer to be ready (normally only 1 ms) int adhocDefaultDelay = 10000; //10000 int adhocExtraDelay = 20000; //20000 int adhocEventPollDelay = 100000; //100000; // Same timings with PSP_ADHOCCTL_RECV_TIMEOUT ? int adhocMatchingEventDelay = 30000; //30000 -int adhocEventDelay = 1000000; //1000000 +int adhocEventDelay = 2000000; //2000000 on real PSP ? u32 defaultLastRecvDelta = 10000; //10000 usec worked well for games published by Falcom (ie. Ys vs Sora Kiseki, Vantage Master Portable) SceUID threadAdhocID; @@ -105,6 +106,7 @@ int matchingInputThread(int matchingId); int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock); int FlushPtpSocket(int socketId); +int NetAdhocGameMode_DeleteMaster(); int NetAdhocctl_ExitGameMode(); static int sceNetAdhocPdpSend(int id, const char* mac, u32 port, void* data, int len, int timeout, int flag); static int sceNetAdhocPdpRecv(int id, void* addr, void* port, void* buf, void* dataLength, u32 timeout, int flag); @@ -146,37 +148,79 @@ static void __GameModeNotify(u64 userdata, int cyclesLate) { SceUID threadID = userdata >> 32; int uid = (int)(userdata & 0xFFFFFFFF); - // TODO: May be we should check for timeout too? since EnterGammeMode have timeout arg if (IsGameModeActive()) { auto sock = adhocSockets[gameModeSocket - 1]; - // Send Master data - if (masterGameModeArea.dataUpdated) { - for (auto& gma : replicaGameModeAreas) { - if (IsGameModeActive() && IsSocketReady(sock->data.pdp.id, false, true) > 0) { - int sent = sceNetAdhocPdpSend(gameModeSocket, (const char*)&gma.mac, ADHOC_GAMEMODE_PORT, masterGameModeArea.data, masterGameModeArea.size, 0, ADHOC_F_NONBLOCK); - if (sent >= 0) { - masterGameModeArea.dataUpdated = 0; - DEBUG_LOG(SCENET, "GameMode Sent %d bytes to %s", masterGameModeArea.size, mac2str(&gma.mac).c_str()); + // Need to make sure all replicas have been created before we start syncing data + if (replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + // Send Master data + if (masterGameModeArea.dataUpdated) { + int sentcount = 0; + for (auto& gma : replicaGameModeAreas) { + if (!gma.dataSent && IsSocketReady(sock->data.pdp.id, false, true) > 0) { + int sent = sceNetAdhocPdpSend(gameModeSocket, (const char*)&gma.mac, ADHOC_GAMEMODE_PORT, masterGameModeArea.data, masterGameModeArea.size, 0, ADHOC_F_NONBLOCK); + if (sent != ERROR_NET_ADHOC_WOULD_BLOCK) { + gma.dataSent = 1; + DEBUG_LOG(SCENET, "GameMode: Master data Sent %d bytes to Area #%d [%s]", masterGameModeArea.size, gma.id, mac2str(&gma.mac).c_str()); + sentcount++; + } + } + else if (gma.dataSent) sentcount++; + } + if (sentcount == replicaGameModeAreas.size()) + masterGameModeArea.dataUpdated = 0; + } + // Need to sync (send + recv) all players initial data (data from CreateMaster) after Master + All Replicas are created, and before the first UpdateMaster / UpdateReplica is called for Star Wars The Force Unleashed to show the correct players color on minimap (also prevent Starting issue on other GameMode games) + else { + u32 error; + SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); + if (error == 0 && waitID == sock->data.pdp.id) { + // Resume thread after all replicas data have been received + int recvd = 0; + for (auto& gma : replicaGameModeAreas) { + // Either replicas new data has been received or that player has been disconnected + if (gma.dataUpdated || gma.updateTimestamp == 0) + recvd++; + } + // Resume blocked thread + u64 now = CoreTiming::GetGlobalTimeUsScaled(); + if (recvd == replicaGameModeAreas.size()) { + u32 waitVal = __KernelGetWaitValue(threadID, error); + if (error == 0) { + DEBUG_LOG(SCENET, "GameMode: Resuming Thread %d after Master data Synced (Result = %08x)", threadID, waitVal); + __KernelResumeThreadFromWait(threadID, waitVal); + } + else + ERROR_LOG(SCENET, "GameMode: Error (%08x) on WaitValue %d ThreadID %d", error, waitVal, threadID); + } + // Attempt to Re-Send initial Master data (in case previous packets were lost) + else if (static_cast(now - masterGameModeArea.updateTimestamp) > GAMEMODE_SYNC_TIMEOUT) { + DEBUG_LOG(SCENET, "GameMode: Attempt to Re-Send Master data after Sync Timeout (%d us)", GAMEMODE_SYNC_TIMEOUT); + // Reset Sent marker on players who haven't replied yet (except disconnected players) + for (auto& gma : replicaGameModeAreas) + if (!gma.dataUpdated && gma.updateTimestamp != 0) + gma.dataSent = 0; + masterGameModeArea.updateTimestamp = now; + masterGameModeArea.dataUpdated = 1; } } } - } - // Recv new Replica data - while (IsGameModeActive() && IsSocketReady(sock->data.pdp.id, true, false) > 0) { - SceNetEtherAddr sendermac; - s32_le senderport = ADHOC_GAMEMODE_PORT; - s32_le bufsz = uid; // GAMEMODE_BUFFER_SIZE; - int ret = sceNetAdhocPdpRecv(gameModeSocket, &sendermac, &senderport, gameModeBuffer, &bufsz, 0, ADHOC_F_NONBLOCK); - if (ret >= 0 && bufsz > 0) { - for (auto& gma : replicaGameModeAreas) { - if (IsMatch(gma.mac, sendermac)) { - DEBUG_LOG(SCENET, "GameMode Received %d bytes of new Data for Area [%s]", bufsz, mac2str(&sendermac).c_str()); - memcpy(gma.data, gameModeBuffer, std::min(gma.size, bufsz)); - gma.dataUpdated = 1; - gma.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); - break; + // Recv new Replica data when available + if (IsSocketReady(sock->data.pdp.id, true, false) > 0) { + SceNetEtherAddr sendermac; + s32_le senderport = ADHOC_GAMEMODE_PORT; + s32_le bufsz = uid; // GAMEMODE_BUFFER_SIZE; + int ret = sceNetAdhocPdpRecv(gameModeSocket, &sendermac, &senderport, gameModeBuffer, &bufsz, 0, ADHOC_F_NONBLOCK); + if (ret >= 0 && bufsz > 0) { + for (auto& gma : replicaGameModeAreas) { + if (IsMatch(gma.mac, sendermac)) { + DEBUG_LOG(SCENET, "GameMode: Replica data Received %d bytes for Area #%d [%s]", bufsz, gma.id, mac2str(&sendermac).c_str()); + memcpy(gma.data, gameModeBuffer, std::min(gma.size, bufsz)); + gma.dataUpdated = 1; + gma.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); + break; + } } } } @@ -197,8 +241,10 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) { u32 error = 0; SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); - if (waitID == 0 || error != 0) + if (waitID == 0 || error != 0) { + WARN_LOG(SCENET, "sceNetAdhocctl Socket WaitID(%i) on Thread(%i) already woken up? (error: %d)", uid, threadID, error); return; + } // Socket not found?! Should never happen! But if it ever happened (ie. loaded from SaveState where adhocctlRequests got cleared) return BUSY and let the game try again. if (adhocctlRequests.find(uid) == adhocctlRequests.end()) { @@ -276,12 +322,20 @@ static void __AdhocctlState(u64 userdata, int cyclesLate) { u32 error = 0; SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); - if (waitID == 0 || error != 0) + if (waitID == 0 || error != 0) { + WARN_LOG(SCENET, "sceNetAdhocctl State WaitID(%i) on Thread(%i) already woken up? (error: %d)", uid, threadID, error); return; + } u32 waitVal = __KernelGetWaitValue(threadID, error); if (error == 0) { adhocctlState = waitVal; + // FIXME: It seems Adhocctl is still busy within the Adhocctl Handler function (ie. during callbacks), + // so we should probably set isAdhocctlBusy to false after mispscall are fully executed (ie. in afterAction). + // But since Adhocctl Handler is optional, there might be cases where there are no handler thus no callback/mipcall being triggered, + // so we should probably need to set isAdhocctlBusy to false here too as a workaround (or may be there is internal handler by default?) + if (adhocctlHandlers.empty()) + isAdhocctlBusy = false; } __KernelResumeThreadFromWait(threadID, result); @@ -333,12 +387,19 @@ int StartGameModeScheduler(int bufSize) { INFO_LOG(SCENET, "GameMode Scheduler (%d, %d) has started", gameModeSocket, bufSize); u64 param = ((u64)__KernelGetCurThread()) << 32 | bufSize; - CoreTiming::ScheduleEvent(usToCycles(GAMEMODE_UPDATE_INTERVAL), gameModeNotifyEvent, param); + CoreTiming::ScheduleEvent(usToCycles(GAMEMODE_INIT_DELAY), gameModeNotifyEvent, param); return 0; } int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { + auto sock = adhocSockets[req.id - 1]; + if (sock->flags & ADHOC_F_ALERTRECV) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTRECV; + return 0; + } + sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); @@ -350,7 +411,8 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { if (ret >= 0 && ret <= *req.length) { ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); // UDP can also receives 0 data, while on TCP receiving 0 data = connection gracefully closed, but not sure whether PDP can send/recv 0 data or not tho - if (ret > 0) { + *req.length = 0; + if (ret >= 0) { DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", req.id, getLocalPort(uid), ret, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); // Peer MAC @@ -384,22 +446,37 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound yet), may need to disable SIO_UDP_CONNRESET error else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK || sockerr == ECONNRESET) { u64 now = (u64)(time_now_d() * 1000000.0); - auto sock = adhocSockets[req.id - 1]; - if (sock->flags & ADHOC_F_ALERTRECV) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTRECV; - sock->alerted_flags |= ADHOC_F_ALERTRECV; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { // Try again later return -1; } else result = ERROR_NET_ADHOC_TIMEOUT; } + // Returning required buffer size when available data in recv buffer is larger than provided buffer size + else if (ret > *req.length) { + WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", req.id, getLocalPort(uid), ret, *req.length, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + *req.length = ret; + + // Peer MAC + SceNetEtherAddr mac; + + // Find Peer MAC + if (resolveIP(sin.sin_addr.s_addr, &mac)) { + // Provide Sender Information + *req.remoteMAC = mac; + *req.remotePort = ntohs(sin.sin_port) - portOffset; + + // FIXME: Do we need to update last recv timestamp? eventhough data hasn't been retrieved yet (ie. peeked) + peerlock.lock(); + auto peer = findFriend(&mac); + if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); + peerlock.unlock(); + } + result = ERROR_NET_ADHOC_NOT_ENOUGH_SPACE; + } else - result = ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED + result = ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED if (ret == SOCKET_ERROR) DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i]: Socket Error (%i)", req.id, sockerr); @@ -410,6 +487,11 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTargets& targetPeers) { auto sock = adhocSockets[req.id - 1]; auto& pdpsocket = sock->data.pdp; + if (sock->flags & ADHOC_F_ALERTSEND) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTSEND; + return 0; + } result = 0; bool retry = false; @@ -431,14 +513,7 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa else { if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTSEND) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTSEND; - sock->alerted_flags |= ADHOC_F_ALERTSEND; - break; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { retry = true; } else @@ -461,6 +536,11 @@ int DoBlockingPdpSend(int uid, AdhocSocketRequest& req, s64& result, AdhocSendTa int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTSEND) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTSEND; + return 0; + } // Send Data int ret = send(uid, (const char*)req.buffer, *req.length, MSG_NOSIGNAL); @@ -478,24 +558,19 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTSEND) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTSEND; - sock->alerted_flags |= ADHOC_F_ALERTSEND; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else result = ERROR_NET_ADHOC_TIMEOUT; } + else { + // Change Socket State. // FIXME: Does Alerted Socket should be closed too? + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - // Change Socket State. // FIXME: Does Alerted Socket should be closed too? - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - result = ERROR_NET_ADHOC_DISCONNECTED; + // Disconnected + result = ERROR_NET_ADHOC_DISCONNECTED; + } if (ret == SOCKET_ERROR) DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i]: Socket Error (%i)", req.id, sockerr); @@ -506,6 +581,11 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTRECV) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTRECV; + return 0; + } int ret = recv(uid, (char*)req.buffer, *req.length, MSG_NOSIGNAL); int sockerr = errno; @@ -526,13 +606,7 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTRECV) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTRECV; - sock->alerted_flags |= ADHOC_F_ALERTRECV; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else @@ -555,6 +629,12 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTACCEPT) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTACCEPT; + return 0; + } + sockaddr_in sin; memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); @@ -576,13 +656,7 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { } else if (ret == 0 || (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK))) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTACCEPT) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTACCEPT; - sock->alerted_flags |= ADHOC_F_ALERTACCEPT; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else { @@ -601,8 +675,13 @@ int DoBlockingPtpAccept(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; - int sockerr; + if (sock->flags & ADHOC_F_ALERTCONNECT) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTCONNECT; + return 0; + } + int sockerr; // Wait for Connection (assuming "connect" has been called before) int ret = IsSocketReady(uid, false, true, &sockerr); @@ -624,13 +703,7 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { // Timeout else if (ret == 0) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTCONNECT) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTCONNECT; - sock->alerted_flags |= ADHOC_F_ALERTCONNECT; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else { @@ -653,34 +726,35 @@ int DoBlockingPtpConnect(int uid, AdhocSocketRequest& req, s64& result) { int DoBlockingPtpFlush(int uid, AdhocSocketRequest& req, s64& result) { auto sock = adhocSockets[req.id - 1]; auto& ptpsocket = sock->data.ptp; + if (sock->flags & ADHOC_F_ALERTFLUSH) { + result = ERROR_NET_ADHOC_SOCKET_ALERTED; + sock->alerted_flags |= ADHOC_F_ALERTFLUSH; + return 0; + } // Try Sending Empty Data int sockerr = FlushPtpSocket(uid); + result = 0; - if (sockerr >= 0) { - result = 0; - return 0; - } - else if (sockerr == EAGAIN || sockerr == EWOULDBLOCK) { + if (sockerr == EAGAIN || sockerr == EWOULDBLOCK) { u64 now = (u64)(time_now_d() * 1000000.0); - if (sock->flags & ADHOC_F_ALERTFLUSH) { - result = ERROR_NET_ADHOC_SOCKET_ALERTED; - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTFLUSH; - sock->alerted_flags |= ADHOC_F_ALERTFLUSH; - } - else if (req.timeout == 0 || now - req.startTime <= req.timeout) { + if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; } else result = ERROR_NET_ADHOC_TIMEOUT; } + else if (isDisconnected(sockerr)) { + // Change Socket State. // FIXME: Does Alerted Socket should be closed too? + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - // Change Socket State. // FIXME: Does Alerted Socket should be closed too? - ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - - // Disconnected - result = ERROR_NET_ADHOC_DISCONNECTED; + // Disconnected + result = ERROR_NET_ADHOC_DISCONNECTED; + } + + if (sockerr != 0) { + DEBUG_LOG(SCENET, "sceNetAdhocPtpFlush[%i]: Socket Error (%i)", req.id, sockerr); + } return 0; } @@ -690,7 +764,8 @@ int DoBlockingAdhocPollSocket(int uid, AdhocSocketRequest& req, s64& result) { int ret = PollAdhocSocket(sds, req.id, 0, 0); if (ret <= 0) { u64 now = (u64)(time_now_d() * 1000000.0); - if (req.timeout == 0 || now - req.startTime <= req.timeout) { + // POSIX poll using negative timeout for indefinitely blocking, not sure about PSP's AdhocPollSocket tho since most of PSP's sceNet API using 0 for indefinitely blocking. + if (static_cast(req.timeout) <= 0 || now - req.startTime <= req.timeout) { return -1; } else if (ret < 0) @@ -725,13 +800,15 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { int delayUS = 500; SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_NET, error); - if (waitID == 0 || error != 0) + if (waitID == 0 || error != 0) { + WARN_LOG(SCENET, "sceNetAdhoc Socket WaitID(%i) on Thread(%i) already woken up? (error: %d)", uid, threadID, error); return; + } - // Socket not found?! Should never happened! but if it ever happen should we just exit here or need to wake the thread first? + // Socket not found?! Should never happened! But if it ever happened (ie. loaded from SaveState where adhocSocketRequests got cleared) return TIMEOUT and let the game try again. if (adhocSocketRequests.find(userdata) == adhocSocketRequests.end()) { WARN_LOG(SCENET, "sceNetAdhoc Socket WaitID(%i) on Thread(%i) not found!", uid, threadID); - //__KernelResumeThreadFromWait(threadID, ERROR_NET_ADHOC_TIMEOUT); + __KernelResumeThreadFromWait(threadID, ERROR_NET_ADHOC_TIMEOUT); return; } @@ -810,7 +887,7 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { } __KernelResumeThreadFromWait(threadID, result); - DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhoc - SocketID: %d", waitID, error, (int)result, req.id); + DEBUG_LOG(SCENET, "Returning (ThreadId: %d, WaitID: %d, error: %d) Result (%08x) of sceNetAdhoc[%d] - SocketID: %d", threadID, waitID, error, (int)result, req.type, req.id); // We are done with this socket adhocSocketRequests.erase(userdata); @@ -820,8 +897,9 @@ static void __AdhocSocketNotify(u64 userdata, int cyclesLate) { int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void* buffer, s32_le* len, u32 timeoutUS, SceNetEtherAddr* remoteMAC, u16_le* remotePort, const char* reason) { int uid = (int)(threadSocketId & 0xFFFFFFFF); if (adhocSocketRequests.find(threadSocketId) != adhocSocketRequests.end()) { - WARN_LOG(SCENET, "sceNetAdhoc - WaitID[%d] already existed, Socket[%d] is busy!", uid, pspSocketId); - return ERROR_NET_ADHOC_BUSY; + WARN_LOG(SCENET, "sceNetAdhoc[%d] - ThreadID[%d] WaitID[%d] already existed, Socket[%d] is busy!", type, static_cast(threadSocketId >> 32), uid, pspSocketId); + // FIXME: Not sure if Adhoc Socket can return ADHOC_BUSY or not (assuming it's similar to EINPROGRESS for Adhoc Socket), or may be we should return TIMEOUT instead? + return ERROR_NET_ADHOC_BUSY; // ERROR_NET_ADHOC_TIMEOUT } if (adhocSocketNotifyEvent < 0) @@ -835,14 +913,27 @@ int WaitBlockingAdhocSocket(u64 threadSocketId, int type, int pspSocketId, void* u64 startTime = (u64)(time_now_d() * 1000000.0); adhocSocketRequests[threadSocketId] = { type, pspSocketId, buffer, len, tmout, startTime, remoteMAC, remotePort }; - // Some games (ie. Power Stone Collection) are using as small as 100 usec timeout - CoreTiming::ScheduleEvent(usToCycles(100), adhocSocketNotifyEvent, threadSocketId); + // Some games (ie. Hitman Reborn Battle Arena 2) are using as small as 50 usec timeout + CoreTiming::ScheduleEvent(usToCycles(1), adhocSocketNotifyEvent, threadSocketId); __KernelWaitCurThread(WAITTYPE_NET, uid, 0, 0, false, reason); // Fallback return value return ERROR_NET_ADHOC_TIMEOUT; } +// Using matchingId = -1 to delete all matching events +void deleteMatchingEvents(const int matchingId = -1) { + for (auto it = matchingEvents.begin(); it != matchingEvents.end(); ) { + if (matchingId < 0 || it->data[0] == matchingId) { + if (Memory::IsValidAddress(it->data[2])) + userMemory.Free(it->data[2]); + it = matchingEvents.erase(it); + } + else + ++it; + } +} + void netAdhocValidateLoopMemory() { // Allocate Memory if it wasn't valid/allocated after loaded from old SaveState if (!dummyThreadHackAddr || (dummyThreadHackAddr && strcmp("dummythreadhack", kernelMemory.GetBlockTag(dummyThreadHackAddr)) != 0)) { @@ -858,7 +949,7 @@ void netAdhocValidateLoopMemory() { } void __NetAdhocDoState(PointerWrap &p) { - auto s = p.Section("sceNetAdhoc", 1, 7); + auto s = p.Section("sceNetAdhoc", 1, 8); if (!s) return; @@ -941,14 +1032,25 @@ void __NetAdhocDoState(PointerWrap &p) { else { adhocctlStateEvent = -1; } + if (s >= 8) { + Do(p, isAdhocctlBusy); + Do(p, netAdhocGameModeEntered); + Do(p, netAdhocEnterGameModeTimeout); + } + else { + isAdhocctlBusy = false; + netAdhocGameModeEntered = false; + netAdhocEnterGameModeTimeout = 15000000; + } if (p.mode == p.MODE_READ) { // Discard leftover events adhocctlEvents.clear(); - matchingEvents.clear(); adhocctlRequests.clear(); adhocSocketRequests.clear(); sendTargetPeers.clear(); + deleteAllAdhocSockets(); + deleteMatchingEvents(); // Let's not change "Inited" value when Loading SaveState to prevent memory & port leaks netAdhocMatchingInited = cur_netAdhocMatchingInited; @@ -1011,6 +1113,12 @@ u32 sceNetAdhocInit() { if (!netAdhocInited) { // Library initialized netAdhocInited = true; + isAdhocctlBusy = false; + + // FIXME: It seems official prx is using sceNetAdhocGameModeDeleteMaster in here? + NetAdhocGameMode_DeleteMaster(); + // Since we are deleting GameMode Master here, we should probably need to make sure GameMode resources all cleared too. + deleteAllGMB(); // Return Success return hleLogSuccessInfoI(SCENET, 0, "at %08x", currentMIPS->pc); @@ -1022,6 +1130,9 @@ u32 sceNetAdhocInit() { static u32 sceNetAdhocctlInit(int stackSize, int prio, u32 productAddr) { INFO_LOG(SCENET, "sceNetAdhocctlInit(%i, %i, %08x) at %08x", stackSize, prio, productAddr, currentMIPS->pc); + // FIXME: Returning 0x8002013a (SCE_KERNEL_ERROR_LIBRARY_NOT_YET_LINKED) without adhoc module loaded first? + // FIXME: Sometimes returning 0x80410601 (ERROR_NET_ADHOC_AUTH_ALREADY_INITIALIZED / Library module is already initialized ?) when AdhocctlTerm is not fully done? + if (netAdhocctlInited) return ERROR_NET_ADHOCCTL_ALREADY_INITIALIZED; @@ -1100,25 +1211,26 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f // Valid Arguments are supplied if (mac != NULL && bufferSize > 0) { // Port is in use by another PDP Socket. - // TODO: Need to test whether using the same PDP port is allowed or not, since PDP/UDP is connectionless it might be allowed. - if (isPDPPortInUse(port)) + if (isPDPPortInUse(port)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this - if (port == 0) port = -(int)portOffset; + if (port == 0) port = -static_cast(portOffset); // Some games (ie. DBZ Shin Budokai 2) might be getting the saddr/srcmac content from SaveState and causing problems :( So we try to fix it here if (saddr != NULL) { getLocalMac(saddr); } - // Valid MAC supplied - if (isLocalMAC(saddr)) { + // Valid MAC supplied. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && isLocalMAC(saddr)) { // Create Internet UDP Socket int usocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Valid Socket produced if (usocket != INVALID_SOCKET) { // Change socket buffer size to be consistent on all platforms. // Send Buffer should be smaller than Recv Buffer to prevent faster device from flooding slower device too much. - setSockBufferSize(usocket, SO_SNDBUF, bufferSize*2); //PSP_ADHOC_PDP_MFS + setSockBufferSize(usocket, SO_SNDBUF, bufferSize*5); //PSP_ADHOC_PDP_MFS // Recv Buffer should be equal or larger than Send Buffer. Using larger Recv Buffer might helped reduces dropped packets during a slowdown, but too large may cause slow performance on Warriors Orochi 2. setSockBufferSize(usocket, SO_RCVBUF, bufferSize*10); //PSP_ADHOC_PDP_MFS*10 @@ -1155,6 +1267,7 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f // Allocated Memory if (internal != NULL) { // Find Free Translator Index + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -1304,6 +1417,12 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int if (timeout > 0) setSockTimeout(pdpsocket.id, SO_SNDTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTSEND) { + socket->alerted_flags |= ADHOC_F_ALERTSEND; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Single Target if (!isBroadcastMAC(daddr)) { // Fill in Target Structure @@ -1329,7 +1448,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; if (sendTargetPeers.find(threadSocketId) != sendTargetPeers.end()) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Socket(%d) is Busy!", id, getLocalPort(pdpsocket.id), pdpsocket.id); - return ERROR_NET_ADHOC_BUSY; + return hleLogError(SCENET, ERROR_NET_ADHOC_BUSY, "busy?"); } AdhocSendTargets dest = { len, {}, false }; @@ -1345,6 +1464,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Free Network Lock //_freeNetworkLock(); + hleEatMicro(1000); // Can be longer than 1ms tho // Sent Data if (sent >= 0) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, getLocalPort(pdpsocket.id), sent, inet_ntoa(target.sin_addr), ntohs(target.sin_port)); @@ -1355,10 +1475,10 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Non-Blocking if (flag) - return ERROR_NET_ADHOC_WOULD_BLOCK; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); // Does PDP can Timeout? There is no concept of Timeout when sending UDP due to no ACK, but might happen if the socket buffer is full, not sure about PDP since some games did use the timeout arg - return ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ADDR; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout?"); // ERROR_NET_ADHOC_INVALID_ADDR; } //VERBOSE_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Unknown Target Peer %s:%u\n", id, getLocalPort(pdpsocket.id), mac2str(daddr, tmpmac), ntohs(target.sin_port)); } @@ -1403,7 +1523,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; if (sendTargetPeers.find(threadSocketId) != sendTargetPeers.end()) { DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u](BC): Socket(%d) is Busy!", id, getLocalPort(pdpsocket.id), pdpsocket.id); - return ERROR_NET_ADHOC_BUSY; + return hleLogError(SCENET, ERROR_NET_ADHOC_BUSY, "busy?"); } sendTargetPeers[threadSocketId] = dest; @@ -1436,34 +1556,34 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int // Free Network Lock //_freeNetworkLock(); + hleEatMicro(1000); // Success, Broadcast never fails! return 0; // len; } } // Invalid Destination Address - return ERROR_NET_ADHOC_INVALID_ADDR; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ADDR, "invalid address"); } // Invalid Argument - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Invalid Socket ID - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Invalid Data Length - return ERROR_NET_ADHOC_INVALID_DATALEN; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_DATALEN, "invalid data length"); } // Invalid Destination Port - return ERROR_NET_ADHOC_INVALID_PORT; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_PORT, "invalid port"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; - + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } /** @@ -1528,6 +1648,12 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * if (timeout > 0) setSockTimeout(pdpsocket.id, SO_RCVTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTRECV) { + socket->alerted_flags |= ADHOC_F_ALERTRECV; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Sender Address sockaddr_in sin; @@ -1568,23 +1694,25 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * // Free Network Lock //_freeNetworkLock(); - return ERROR_NET_ADHOC_NOT_ENOUGH_SPACE; //received; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_ENOUGH_SPACE, "not enough space"); //received; } received = recvfrom(pdpsocket.id, (char*)buf, *len, MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); error = errno; - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK)) { + // On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET)) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | pdpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv"); } - VERBOSE_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPdpRecv[%i:%u] [size=%i]", error, id, pdpsocket.lport, *len); + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); } + hleEatMicro(1000); // Received Data. UDP can also receives 0 data, while on TCP 0 data = connection gracefully closed, but not sure about PDP tho - if (received > 0) { + if (received >= 0) { DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); // Peer MAC @@ -1633,29 +1761,22 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * if (wouldblock) flag = 1; #endif - // Nothing received. On Windows: recvfrom on UDP can get error WSAECONNRESET when previous sendto's destination is unreachable (or destination port is not bound), may need to disable SIO_UDP_CONNRESET - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ECONNRESET || error == ETIMEDOUT)) { - // Blocking Situation - if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; + DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Result:%i (Error:%i)", id, pdpsocket.lport, received, error); - // Timeout - return ERROR_NET_ADHOC_TIMEOUT; - } - - // Disconnected - return ERROR_NET_ADHOC_DISCONNECTED; + // Timeout? + return hleLogError(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout"); } // Invalid Argument - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Invalid Socket ID - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } int NetAdhoc_SetSocketAlert(int id, s32_le flag) { @@ -1668,7 +1789,7 @@ int NetAdhoc_SetSocketAlert(int id, s32_le flag) { adhocSockets[id - 1]->flags = flg; adhocSockets[id - 1]->alerted_flags = 0; - return 0; + return hleDelayResult(0, "set socket alert delay", 1000); } // Flags seems to be bitmasks of ADHOC_F_ALERT... (need more games to test this) @@ -1733,7 +1854,7 @@ int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock } // Fate Unlimited Codes and Carnage Heart EXA relies on AdhocPollSocket in order to retry a failed PtpConnect, but the interval must not be too long (about 1 frame before state became Established by GetPtpStat) for Bleach Heat the Soul 7 to work properly. else if ((sds[i].events & ADHOC_EV_CONNECT) && ((sock->data.ptp.state == ADHOC_PTP_STATE_CLOSED && sock->attemptCount == 0) || - (sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT && (CoreTiming::GetGlobalTimeUsScaled() - sock->lastAttempt > 1000/*std::max(1000, sock->retry_interval - 60000)*/)))) { + (sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT && (static_cast(CoreTiming::GetGlobalTimeUsScaled() - sock->lastAttempt) > 1000/*std::max(1000, sock->retry_interval - 60000)*/)))) { sds[i].revents |= ADHOC_EV_CONNECT; } @@ -1747,8 +1868,6 @@ int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock } if (sock->flags & ADHOC_F_ALERTPOLL) { - // FIXME: Should we clear the flag after alert signaled? - sock->flags &= ~ADHOC_F_ALERTPOLL; sock->alerted_flags |= ADHOC_F_ALERTPOLL; return ERROR_NET_ADHOC_SOCKET_ALERTED; @@ -1794,12 +1913,6 @@ int sceNetAdhocPollSocket(u32 socketStructAddr, int count, int timeout, int nonb if (nonblock) timeout = 0; - // Blocking Mode - else - // Does timeout = 0 means indefinite on PSP? - if (timeout <= 0) - timeout = adhocDefaultTimeout; // minSocketTimeoutUS; - if (count > (int)FD_SETSIZE) count = FD_SETSIZE; // return 0; //ERROR_NET_ADHOC_INVALID_ARG @@ -1810,7 +1923,7 @@ int sceNetAdhocPollSocket(u32 socketStructAddr, int count, int timeout, int nonb //int affectedsockets = sceNetInetPoll(isds, count, timeout); int affectedsockets = 0; if (nonblock) - affectedsockets = PollAdhocSocket(sds, count, timeout, nonblock); + affectedsockets = PollAdhocSocket(sds, count, 0, nonblock); else { // Simulate blocking behaviour with non-blocking socket // Borrowing some arguments to pass some parameters. The dummy WaitID(count+1) might not be unique thus have duplicate possibilities if there are multiple thread trying to poll the same numbers of socket at the same time @@ -1913,7 +2026,7 @@ static int sceNetAdhocPdpDelete(int id, int unknown) { } static int sceNetAdhocctlGetAdhocId(u32 productStructAddr) { - ERROR_LOG(SCENET, "UNIMPL sceNetAdhocctlGetAdhocId(%08x)", productStructAddr); + INFO_LOG(SCENET, "sceNetAdhocctlGetAdhocId(%08x) at %08x", productStructAddr, currentMIPS->pc); // Library initialized if (netAdhocctlInited) @@ -1921,21 +2034,19 @@ static int sceNetAdhocctlGetAdhocId(u32 productStructAddr) { // Valid Arguments if (Memory::IsValidAddress(productStructAddr)) { - SceNetAdhocctlAdhocId * adhoc_id = (SceNetAdhocctlAdhocId *)Memory::GetPointer(productStructAddr); // Copy Product ID - *adhoc_id = product_code; - //Memory::WriteStruct(productStructAddr, &product_code); + Memory::WriteStruct(productStructAddr, &product_code); // Return Success - return 0; + return hleLogDebug(SCENET, 0, "type = %d, code = %s", product_code.type, product_code.data); } // Invalid Arguments - return ERROR_NET_ADHOCCTL_INVALID_ARG; + return hleLogDebug(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); } // Library uninitialized - return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; + return hleLogDebug(SCENET, ERROR_NET_ADHOCCTL_NOT_INITIALIZED, "not initialized"); } // FIXME: Scan probably not a blocking function since there is ADHOCCTL_STATE_SCANNING state that can be polled by the game, right? But apparently it need to be delayed for Naruto Shippuden Ultimate Ninja Heroes 3 @@ -1947,11 +2058,20 @@ int sceNetAdhocctlScan() { // Library initialized if (netAdhocctlInited) { - int us = adhocDefaultDelay * 2; + int us = adhocDefaultDelay; + // FIXME: When tested with JPCSP + official prx files it seems when adhocctl in a connected state (ie. joined to a group) attempting to create/connect/join/scan will return a success (without doing anything?) + if ((adhocctlState == ADHOCCTL_STATE_CONNECTED) || (adhocctlState == ADHOCCTL_STATE_GAMEMODE)) { + // TODO: Valhalla Knights 2 need handler notification, but need to test this on games that doesn't use Adhocctl Handler too (not sure if there are games like that tho) + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOCCTL_ALREADY_CONNECTED); + hleEatMicro(500); + return 0; + } // Only scan when in Disconnected state, otherwise AdhocServer will kick you out - if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED) { + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED && !isAdhocctlBusy) { + isAdhocctlBusy = true; adhocctlState = ADHOCCTL_STATE_SCANNING; + adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL; // Reset Networks/Group list to prevent other threads from using these soon to be replaced networks peerlock.lock(); @@ -1971,33 +2091,27 @@ int sceNetAdhocctlScan() { ERROR_LOG(SCENET, "Socket error (%i) when sending", error); adhocctlState = ADHOCCTL_STATE_DISCONNECTED; //if (error == ECONNABORTED || error == ECONNRESET || error == ENOTCONN) return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // A case where it need to reconnect to AdhocServer - return ERROR_NET_ADHOCCTL_BUSY; + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_BUSY, "busy"); } else if (friendFinderRunning) { AdhocctlRequest req = { OPCODE_SCAN, {0} }; return WaitBlockingAdhocctlSocket(req, us, "adhocctl scan"); } } - else { - // Return Success and let friendFinder thread to notify the handler when scan completed - // Not delaying here may cause Naruto Shippuden Ultimate Ninja Heroes 3 to get disconnected when the mission started - hleEatMicro(us); - return 0; - } - } - else if (adhocctlState == ADHOCCTL_STATE_SCANNING) - return ERROR_NET_ADHOCCTL_BUSY; - // Already connected to a group. Should we fake a success? - // We need to notify the handler on success, even if it was faked - notifyAdhocctlHandlers(ADHOCCTL_EVENT_SCAN, 0); - // FIXME: returning ERROR_NET_ADHOCCTL_BUSY may trigger the game (ie. Ford Street Racing) to call sceNetAdhocctlDisconnect, But Not returning a Success(0) will cause Valhalla Knights 2 not working properly - hleEatMicro(us); - return 0; + // Return Success and let friendFinder thread to notify the handler when scan completed + // Not delaying here may cause Naruto Shippuden Ultimate Ninja Heroes 3 to get disconnected when the mission started + hleEatMicro(us); + // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlScan switching to a different thread for at least 100ms after returning success and before executing the next line? + return hleDelayResult(0, "scan delay", adhocEventPollDelay); + } + + // FIXME: Returning BUSY when previous adhocctl handler's callback is not fully executed yet, But returning success and notifying handler's callback with error (ie. ALREADY_CONNECTED) when previous adhocctl handler's callback is fully executed? Is there a case where error = BUSY sent through handler's callback? + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_BUSY, "busy"); } // Library uninitialized - return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOCCTL_NOT_INITIALIZED, "not initialized"); } int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { @@ -2006,7 +2120,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { SceNetAdhocctlScanInfoEmu *buf = NULL; if (Memory::IsValidAddress(bufAddr)) buf = (SceNetAdhocctlScanInfoEmu *)Memory::GetPointer(bufAddr); - INFO_LOG(SCENET, "sceNetAdhocctlGetScanInfo([%08x]=%i, %08x)", sizeAddr, Memory::Read_U32(sizeAddr), bufAddr); + INFO_LOG(SCENET, "sceNetAdhocctlGetScanInfo([%08x]=%i, %08x) at %08x", sizeAddr, Memory::Read_U32(sizeAddr), bufAddr, currentMIPS->pc); if (!g_Config.bEnableWlan) { return 0; } @@ -2018,12 +2132,20 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { // Minimum Argument Requirements if (buflen != NULL) { + // FIXME: Do we need to exclude Groups created by this device it's self? + bool excludeSelf = false; + // Multithreading Lock peerlock.lock(); + // FIXME: When already connected to a group GetScanInfo will return size = 0 ? or may be only hides the group created by it's self? + if (adhocctlState == ADHOCCTL_STATE_CONNECTED || adhocctlState == ADHOCCTL_STATE_GAMEMODE) { + *buflen = 0; + DEBUG_LOG(SCENET, "NetworkList [Available: 0] Already in a Group"); + } // Length Returner Mode - if (buf == NULL) { - int availNetworks = countAvailableNetworks(); + else if (buf == NULL) { + int availNetworks = countAvailableNetworks(excludeSelf); *buflen = availNetworks * sizeof(SceNetAdhocctlScanInfoEmu); DEBUG_LOG(SCENET, "NetworkList [Available: %i]", availNetworks); } @@ -2044,7 +2166,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { SceNetAdhocctlScanInfo * group = networks; // Iterate Group List - for (; group != NULL && discovered < requestcount; group = group->next) { + for (; group != NULL && (!excludeSelf || !isLocalMAC(&group->bssid.mac_addr)) && discovered < requestcount; group = group->next) { // Copy Group Information //buf[discovered] = *group; buf[discovered].group_name = group->group_name; @@ -2079,6 +2201,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) { // Multithreading Unlock peerlock.unlock(); + hleEatMicro(2000); // Return Success return 0; } @@ -2137,9 +2260,17 @@ u32 NetAdhocctl_Disconnect() { // Library initialized if (netAdhocctlInited) { int iResult, error; - int us = adhocDefaultDelay * 5; - // Connected State (Adhoc Mode) - if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { // (threadStatus == ADHOCCTL_STATE_CONNECTED) + int us = adhocDefaultDelay * 3; + hleEatMicro(1000); + + if (isAdhocctlBusy) { + return ERROR_NET_ADHOCCTL_BUSY; + } + + // Connected State (Adhoc Mode). Attempting to leave a group while not in a group will be kicked out by Adhoc Server (ie. some games tries to disconnect more than once within a short time) + if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { + isAdhocctlBusy = true; + // Clear Network Name memset(¶meter.group_name, 0, sizeof(parameter.group_name)); @@ -2169,7 +2300,6 @@ u32 NetAdhocctl_Disconnect() { } else { // Set Disconnected State - //adhocctlState = ADHOCCTL_STATE_DISCONNECTED; return ERROR_NET_ADHOCCTL_BUSY; } } @@ -2199,12 +2329,16 @@ u32 NetAdhocctl_Disconnect() { adhocctlCurrentMode = ADHOCCTL_MODE_NONE; // Notify Event Handlers (even if we weren't connected, not doing this will freeze games like God Eater, which expect this behaviour) - notifyAdhocctlHandlers(ADHOCCTL_EVENT_DISCONNECT, 0); - //hleCheckCurrentCallbacks(); + // FIXME: When there are no handler the state will immediately became ADHOCCTL_STATE_DISCONNECTED ? + if (adhocctlHandlers.empty()) { + adhocctlState = ADHOCCTL_STATE_DISCONNECTED; + } + else { + notifyAdhocctlHandlers(ADHOCCTL_EVENT_DISCONNECT, 0); + } - hleEatMicro(us); // Return Success, some games might ignore returned value and always treat it as success, otherwise repeatedly calling this function - return 0; + return hleDelayResult(0, "disconnect delay", us); } // Library uninitialized @@ -2250,6 +2384,10 @@ int NetAdhocctl_Term() { friendFinderThread.join(); } + // Clear GameMode resources + NetAdhocGameMode_DeleteMaster(); + deleteAllGMB(); + // Clear Peer List int32_t peercount = 0; freeFriendsRecursive(friends, &peercount); @@ -2271,6 +2409,7 @@ int NetAdhocctl_Term() { } threadAdhocID = 0; adhocctlCurrentMode = ADHOCCTL_MODE_NONE; + isAdhocctlBusy = false; netAdhocctlInited = false; } @@ -2383,7 +2522,7 @@ int sceNetAdhocctlGetPeerInfo(const char *mac, int size, u32 peerInfoAddr) { buf->mac_addr = *maddr; buf->flags = 0x0400; buf->padding = 0; - buf->last_recv = CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta; + buf->last_recv = std::max(0LL, static_cast(CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta)); // Success retval = 0; @@ -2414,6 +2553,7 @@ int sceNetAdhocctlGetPeerInfo(const char *mac, int size, u32 peerInfoAddr) { // Multithreading Unlock peerlock.unlock(); } + hleEatMicro(50); return retval; } @@ -2427,8 +2567,18 @@ int NetAdhocctl_Create(const char* groupName) { if (netAdhocctlInited) { // Valid Argument if (validNetworkName(groupNameStruct)) { - // Disconnected State, may also need to check for Scanning state to prevent some games from failing to host a game session - if ((adhocctlState == ADHOCCTL_STATE_DISCONNECTED) || (adhocctlState == ADHOCCTL_STATE_SCANNING)) { + // FIXME: When tested with JPCSP + official prx files it seems when adhocctl in a connected state (ie. joined to a group) attempting to create/connect/join/scan will return a success (without doing anything?) + if ((adhocctlState == ADHOCCTL_STATE_CONNECTED) || (adhocctlState == ADHOCCTL_STATE_GAMEMODE)) { + // TODO: Need to test this on games that doesn't use Adhocctl Handler too (not sure if there are games like that tho) + notifyAdhocctlHandlers(ADHOCCTL_EVENT_ERROR, ERROR_NET_ADHOCCTL_ALREADY_CONNECTED); + hleEatMicro(500); + return 0; + } + + // Disconnected State + if (adhocctlState == ADHOCCTL_STATE_DISCONNECTED && !isAdhocctlBusy) { + isAdhocctlBusy = true; + // Set Network Name if (groupNameStruct != NULL) parameter.group_name = *groupNameStruct; @@ -2452,10 +2602,11 @@ int NetAdhocctl_Create(const char* groupName) { // Send Packet int iResult = send(metasocket, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL); int error = errno; + adhocctlStartTime = (u64)(time_now_d() * 1000000.0); if (iResult == SOCKET_ERROR && error != EAGAIN && error != EWOULDBLOCK) { ERROR_LOG(SCENET, "Socket error (%i) when sending", error); - //return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; // ERROR_NET_ADHOCCTL_DISCONNECTED; // ERROR_NET_ADHOCCTL_BUSY; + //Faking success, to prevent Full Auto 2 from freezing while Initializing Network if (adhocctlCurrentMode == ADHOCCTL_MODE_GAMEMODE) { adhocctlState = ADHOCCTL_STATE_GAMEMODE; @@ -2476,7 +2627,7 @@ int NetAdhocctl_Create(const char* groupName) { // Wait for Status to be connected to prevent Ford Street Racing from Failed to create game session int us = adhocDefaultDelay; - if (adhocctlState != ADHOCCTL_STATE_CONNECTED && iResult == SOCKET_ERROR && friendFinderRunning) { + if (adhocctlState != ADHOCCTL_STATE_CONNECTED && adhocctlState != ADHOCCTL_STATE_GAMEMODE && iResult == SOCKET_ERROR && friendFinderRunning) { AdhocctlRequest req = { OPCODE_CONNECT, {0} }; if (groupNameStruct != NULL) req.group = *groupNameStruct; return WaitBlockingAdhocctlSocket(req, us, "adhocctl connect"); @@ -2484,7 +2635,8 @@ int NetAdhocctl_Create(const char* groupName) { hleEatMicro(us); // Return Success - return 0; + // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlCreate switching to a different thread for at least 100ms after returning success and before executing the next line. + return hleDelayResult(0, "create/connect/join delay", adhocEventPollDelay); } // Connected State @@ -2570,12 +2722,12 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n if (!Memory::IsValidAddress(membersAddr)) return ERROR_NET_ADHOCCTL_INVALID_ARG; - if (game_type <= 0 || game_type > 3 || num_members < 2 || num_members > 16 || (game_type == 1 && num_members > 4)) + if (game_type < ADHOCCTL_GAMETYPE_1A || game_type > ADHOCCTL_GAMETYPE_2A || num_members < 2 || num_members > 16 || (game_type == ADHOCCTL_GAMETYPE_1A && num_members > 4)) return ERROR_NET_ADHOCCTL_INVALID_ARG; + deleteAllGMB(); + SceNetEtherAddr* addrs = PSPPointer::Create(membersAddr); // List of participating MAC addresses (started from host) - gameModeMacs.clear(); - requiredGameModeMacs.clear(); for (int i = 0; i < num_members; i++) { requiredGameModeMacs.push_back(*addrs); DEBUG_LOG(SCENET, "GameMode macAddress#%d=%s", i, mac2str(addrs).c_str()); @@ -2586,10 +2738,19 @@ int NetAdhocctl_CreateEnterGameMode(const char* group_name, int game_type, int n getLocalMac(&localMac); gameModeMacs.push_back(localMac); + // FIXME: There seems to be an internal Adhocctl Handler on official prx (running on "SceNetAdhocctl" thread) that will try to sync GameMode timings, by using blocking PTP socket: + // 1). PtpListen (srcMacAddress=0x09F20CB4, srcPort=0x8001, bufSize=0x2000, retryDelay=0x30D40, retryCount=0x33, queue=0x1, unk1=0x0) + // 2). PtpAccpet (peerMacAddr=0x09FE2020, peerPortAddr=0x09FE2010, timeout=0x765BB0, nonblock=0x0) - probably for each clients + // 3). PtpSend (data=0x09F20E18, dataSizeAddr=0x09FE2094, timeout=0x627EDA, nonblock=0x0) - not sure what kind of data nor the size (more than 6 bytes) + // 4). PtpFlush (timeout=0x2DC6C0, nonblock=0x0) + // 5 & 6). PtpClose (accepted socket & listen socket) + // When timeout reached, notify user-defined Adhocctl Handlers with ERROR event (ERROR_NET_ADHOC_TIMEOUT) instead of GAMEMODE event + // We have to wait for all the MACs to have joined to go into CONNECTED state adhocctlCurrentMode = ADHOCCTL_MODE_GAMEMODE; adhocConnectionType = ADHOC_CREATE; netAdhocGameModeEntered = true; + netAdhocEnterGameModeTimeout = timeout; return NetAdhocctl_Create(group_name); } @@ -2636,12 +2797,22 @@ static int sceNetAdhocctlJoinEnterGameMode(const char * group_name, const char * if (!hostMac) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + deleteAllGMB(); + // Add host mac first gameModeMacs.push_back(*(SceNetEtherAddr*)hostMac); + // FIXME: There seems to be an internal Adhocctl Handler on official prx (running on "SceNetAdhocctl" thread) that will try to sync GameMode timings, by using blocking PTP socket: + // 1). PtpOpen (srcMacAddress=0x09FE2080, srcPort=0x8001, destMacAddress=0x09F20CB4, destPort=0x8001, bufSize=0x2000, retryDelay=0x30D40, retryCount=0x33, unk1=0x0) + // 2). PtpConnect (timeout=0x874CAC, nonblock=0x0) - to host/creator + // 3). PtpRecv (data=0x09F20E18, dataSizeAddr=0x09FE2044, timeout=0x647553, nonblock=0x0) - repeated until data fully received with data address/offset adjusted (increased) and timeout adjusted (decreased), probably also adjusted data size (decreased) on each call + // 4). PtpClose + // When timeout reached, notify user-defined Adhocctl Handlers with ERROR event (ERROR_NET_ADHOC_TIMEOUT) instead of GAMEMODE event + adhocctlCurrentMode = ADHOCCTL_MODE_GAMEMODE; adhocConnectionType = ADHOC_JOIN; netAdhocGameModeEntered = true; + netAdhocEnterGameModeTimeout = timeout; return hleLogDebug(SCENET, NetAdhocctl_Create(group_name), ""); } @@ -2674,12 +2845,12 @@ int NetAdhoc_Term() { // Library is initialized if (netAdhocInited) { - // Delete Adhoc Sockets - deleteAllAdhocSockets(); - // Delete GameMode Buffers deleteAllGMB(); + // Delete Adhoc Sockets + deleteAllAdhocSockets(); + // Terminate Internet Library //sceNetInetTerm(); @@ -2833,7 +3004,7 @@ static int sceNetAdhocGetPtpStat(u32 structSize, u32 structAddr) { if ( sock != NULL && sock->type == SOCK_PTP) { // Update connection state. // GvG Next Plus relies on GetPtpStat to determine if Connection has been Established or not, but should not be updated too long for GvG to work, and should not be updated too fast(need to be 1 frame after PollSocket checking for ADHOC_EV_CONNECT) for Bleach Heat the Soul 7 to work properly. - if ((sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT || sock->data.ptp.state == ADHOC_PTP_STATE_SYN_RCVD) && (CoreTiming::GetGlobalTimeUsScaled() - sock->lastAttempt > 35000/*sock->retry_interval*/)) { + if ((sock->data.ptp.state == ADHOC_PTP_STATE_SYN_SENT || sock->data.ptp.state == ADHOC_PTP_STATE_SYN_RCVD) && (static_cast(CoreTiming::GetGlobalTimeUsScaled() - sock->lastAttempt) > 35000/*sock->retry_interval*/)) { // FIXME: May be we should poll all of them together on a single poll call instead of each socket separately? if (IsSocketReady(sock->data.ptp.id, true, true) > 0) { sock->data.ptp.state = ADHOC_PTP_STATE_ESTABLISHED; @@ -2906,17 +3077,19 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, if (saddr != NULL) { getLocalMac(saddr); } - // Valid Addresses - if (saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr)) { + // Valid Addresses. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr) && !isZeroMAC(daddr)) { // Dissidia 012 will try to reOpen the port without Closing the old one first when PtpConnect failed to try again. - if (isPTPPortInUse(sport, false)) + if (isPTPPortInUse(sport, false)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } // Random Port required if (sport == 0) { isClient = true; //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this - sport = -(int)portOffset; + sport = -static_cast(portOffset); } // Valid Arguments @@ -2926,9 +3099,12 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Valid Socket produced if (tcpsocket > 0) { + // Change socket MSS + setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); + // Change socket buffer size to be consistent on all platforms. - setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*2); //PSP_ADHOC_PTP_MFS - setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MFS*10 + setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS + setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 // Enable KeepAlive setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); @@ -2970,6 +3146,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3073,6 +3250,7 @@ int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEther // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3094,9 +3272,12 @@ int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEther // Copy Socket Descriptor to Structure internal->data.ptp.id = newsocket; + // Change socket MSS + setSockMSS(newsocket, PSP_ADHOC_PTP_MSS); + // Set Default Buffer Size or inherit the size? internal->buffer_size = socket->buffer_size; - setSockBufferSize(newsocket, SO_SNDBUF, internal->buffer_size*2); //PSP_ADHOC_PTP_MSS + setSockBufferSize(newsocket, SO_SNDBUF, internal->buffer_size*5); //PSP_ADHOC_PTP_MSS setSockBufferSize(newsocket, SO_RCVBUF, internal->buffer_size*10); //PSP_ADHOC_PTP_MSS*10 // Copy Local Address Data to Structure @@ -3182,8 +3363,15 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int auto& ptpsocket = socket->data.ptp; socket->nonblocking = flag; + if (socket->flags & ADHOC_F_ALERTACCEPT) { + socket->alerted_flags |= ADHOC_F_ALERTACCEPT; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Listener Socket if (ptpsocket.state == ADHOC_PTP_STATE_LISTEN) { + hleEatMicro(500); // Address Information sockaddr_in peeraddr; memset(&peeraddr, 0, sizeof(peeraddr)); @@ -3198,7 +3386,7 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int error = errno; } - if (newsocket == 0 || (newsocket == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ETIMEDOUT))) { + if (newsocket == 0 || (newsocket == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK))) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; @@ -3219,26 +3407,26 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int // Action would block if (flag) - return ERROR_NET_ADHOC_WOULD_BLOCK; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); // Timeout - return ERROR_NET_ADHOC_TIMEOUT; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout"); } // Client Socket - return ERROR_NET_ADHOC_NOT_LISTENED; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_LISTENED, "not listened"); } // Invalid Socket - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Invalid Arguments - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } /** @@ -3251,7 +3439,7 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { INFO_LOG(SCENET, "sceNetAdhocPtpConnect(%i, %i, %i) at %08x", id, timeout, flag, currentMIPS->pc); if (!g_Config.bEnableWlan) { - return 0; + return -1; } // Library is initialized @@ -3264,12 +3452,19 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { auto& ptpsocket = socket->data.ptp; socket->nonblocking = flag; + if (socket->flags & ADHOC_F_ALERTCONNECT) { + socket->alerted_flags |= ADHOC_F_ALERTCONNECT; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Phantasy Star Portable 2 will try to reconnect even when previous connect already success, so we should return success too if it's already connected if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) return 0; // Valid Client Socket if (ptpsocket.state == ADHOC_PTP_STATE_CLOSED || ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) { + hleEatMicro(500); // Target Address sockaddr_in sin; memset(&sin, 0, sizeof(sin)); @@ -3310,42 +3505,46 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { return 0; } - // Connection in Progress - else if (connectresult == SOCKET_ERROR && connectInProgress(errorcode)) { - socket->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; - socket->attemptCount++; - socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); - // Blocking Mode - // Workaround: Forcing first attempt to be blocking to prevent issue related to lobby or high latency networks. (can be useful for GvG Next Plus, Dissidia 012, and Fate Unlimited Codes) - if (!flag || (g_Config.bForcedFirstConnect && socket->attemptCount == 1)) { - // Simulate blocking behaviour with non-blocking socket - u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; - return WaitBlockingAdhocSocket(threadSocketId, PTP_CONNECT, id, nullptr, nullptr, (flag) ? std::max((int)socket->retry_interval, timeout) : timeout, nullptr, nullptr, "ptp connect"); + // Error handling + else if (connectresult == SOCKET_ERROR) { + // Connection in Progress + if (connectInProgress(errorcode)) { + socket->data.ptp.state = ADHOC_PTP_STATE_SYN_SENT; + socket->attemptCount++; + socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); + // Blocking Mode + // Workaround: Forcing first attempt to be blocking to prevent issue related to lobby or high latency networks. (can be useful for GvG Next Plus, Dissidia 012, and Fate Unlimited Codes) + if (!flag || (g_Config.bForcedFirstConnect && socket->attemptCount == 1)) { + // Simulate blocking behaviour with non-blocking socket + u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; + return WaitBlockingAdhocSocket(threadSocketId, PTP_CONNECT, id, nullptr, nullptr, (flag) ? std::max((int)socket->retry_interval, timeout) : timeout, nullptr, nullptr, "ptp connect"); + } + // NonBlocking Mode + else { + return hleLogDebug(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); + } } - // NonBlocking Mode - else { - return ERROR_NET_ADHOC_WOULD_BLOCK; + // No connection could be made because the target device actively refused it. + else if (errorcode == ECONNREFUSED) { + return hleLogError(SCENET, ERROR_NET_ADHOC_CONNECTION_REFUSED, "connection refused"); } } } // Peer not found - if (flag) - return ERROR_NET_ADHOC_WOULD_BLOCK; - - return ERROR_NET_ADHOC_CONNECTION_REFUSED; // ERROR_NET_ADHOC_TIMEOUT; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_INVALID_ADDR, "invalid address"); // ERROR_NET_ADHOC_WOULD_BLOCK / ERROR_NET_ADHOC_TIMEOUT } // Not a valid Client Socket - return ERROR_NET_ADHOC_NOT_OPENED; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_NOT_OPENED, "not opened"); } // Invalid Socket - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogDebug(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } int NetAdhocPtp_Close(int id, int unknown) { @@ -3426,16 +3625,18 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int if (saddr != NULL) { getLocalMac(saddr); } - // Valid Address - if (saddr != NULL && isLocalMAC(saddr)) { + // Valid Address. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) + if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && saddr != NULL && isLocalMAC(saddr)) { // It's allowed to Listen and Open the same PTP port, But it's not allowed to Listen or Open the same PTP port twice. - if (isPTPPortInUse(sport, true)) + if (isPTPPortInUse(sport, true)) { + // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); + } // Random Port required if (sport == 0) { //sport 0 should be shifted back to 0 when using offset Phantasy Star Portable 2 use this - sport = -(int)portOffset; + sport = -static_cast(portOffset); } // Valid Arguments @@ -3446,9 +3647,12 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Valid Socket produced if (tcpsocket > 0) { + // Change socket MSS + setSockMSS(tcpsocket, PSP_ADHOC_PTP_MSS); + // Change socket buffer size to be consistent on all platforms. - setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*2); //PSP_ADHOC_PTP_MFS - setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MFS*10 + setSockBufferSize(tcpsocket, SO_SNDBUF, bufsize*5); //PSP_ADHOC_PTP_MSS + setSockBufferSize(tcpsocket, SO_RCVBUF, bufsize*10); //PSP_ADHOC_PTP_MSS*10 // Enable KeepAlive setSockKeepAlive(tcpsocket, true, rexmt_int / 1000000L, rexmt_cnt); @@ -3491,6 +3695,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int // Allocated Memory if (internal != NULL) { // Find Free Translator ID + // FIXME: We should probably use an increasing index instead of looking for an empty slot from beginning if we want to simulate a real socket id int i = 0; for (; i < MAX_SOCKET; i++) if (adhocSockets[i] == NULL) break; @@ -3579,9 +3784,7 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int */ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeout, int flag) { DEBUG_LOG(SCENET, "sceNetAdhocPtpSend(%d,%08x,%08x,%d,%d) at %08x", id, dataAddr, dataSizeAddr, timeout, flag, currentMIPS->pc); - if (!g_Config.bEnableWlan) { - return 0; - } + int * len = (int *)Memory::GetPointer(dataSizeAddr); const char * data = Memory::GetCharPointer(dataAddr); // Library is initialized @@ -3603,6 +3806,12 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Apply Send Timeout Settings to Socket if (timeout > 0) setSockTimeout(ptpsocket.id, SO_SNDTIMEO, timeout); + + if (socket->flags & ADHOC_F_ALERTSEND) { + socket->alerted_flags |= ADHOC_F_ALERTSEND; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } // Acquire Network Lock // _acquireNetworkLock(); @@ -3630,34 +3839,36 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK)) { // Non-Blocking if (flag) - return ERROR_NET_ADHOC_WOULD_BLOCK; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PTP_SEND, id, (void*)data, len, timeout, nullptr, nullptr, "ptp send"); } + + DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u -> %s:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport, sent, error); // Change Socket State ptpsocket.state = ADHOC_PTP_STATE_CLOSED; // Disconnected - return ERROR_NET_ADHOC_DISCONNECTED; + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); } // Invalid Arguments - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } // Not connected - return ERROR_NET_ADHOC_NOT_CONNECTED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CONNECTED, "not connected"); } // Invalid Socket - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } /** @@ -3671,9 +3882,7 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou */ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeout, int flag) { DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv(%d,%08x,%08x,%d,%d) at %08x", id, dataAddr, dataSizeAddr, timeout, flag, currentMIPS->pc); - if (!g_Config.bEnableWlan) { - return 0; - } + void * buf = (void *)Memory::GetPointer(dataAddr); int * len = (int *)Memory::GetPointer(dataSizeAddr); // Library is initialized @@ -3695,6 +3904,12 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou if (timeout > 0) setSockTimeout(ptpsocket.id, SO_RCVTIMEO, timeout); + if (socket->flags & ADHOC_F_ALERTRECV) { + socket->alerted_flags |= ADHOC_F_ALERTRECV; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Acquire Network Lock // _acquireNetworkLock(); @@ -3713,7 +3928,7 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou return WaitBlockingAdhocSocket(threadSocketId, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv"); } - VERBOSE_LOG(SCENET, "Socket Error (%i) on sceNetAdhocPtpRecv[%i:%u] [size=%i]", error, id, ptpsocket.lport, *len); + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); } // Free Network Lock @@ -3738,36 +3953,31 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou return 0; } - // Non-Critical Error - else if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || error == ETIMEDOUT)) { - // Blocking Situation - if (flag) return ERROR_NET_ADHOC_WOULD_BLOCK; - - // Timeout - return hleLogError(SCENET, ERROR_NET_ADHOC_TIMEOUT, "ptp recv timeout"); - } DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Result:%i (Error:%i)", id, ptpsocket.lport, received, error); + if (*len == 0) + return 0; + // Change Socket State ptpsocket.state = ADHOC_PTP_STATE_CLOSED; // Disconnected - return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "ptp recv disconnected"); + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); } - return ERROR_NET_ADHOC_NOT_CONNECTED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CONNECTED, "not connected"); } // Invalid Socket - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Invalid Arguments - return ERROR_NET_ADHOC_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid socket arg"); } // Library is uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } int FlushPtpSocket(int socketId) { @@ -3798,9 +4008,6 @@ int FlushPtpSocket(int socketId) { */ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { DEBUG_LOG(SCENET,"sceNetAdhocPtpFlush(%d,%d,%d) at %08x", id, timeout, nonblock, currentMIPS->pc); - if (!g_Config.bEnableWlan) { - return 0; - } // Library initialized if (netAdhocInited) { @@ -3811,23 +4018,40 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { auto& ptpsocket = socket->data.ptp; socket->nonblocking = nonblock; + if (socket->flags & ADHOC_F_ALERTFLUSH) { + socket->alerted_flags |= ADHOC_F_ALERTFLUSH; + + return hleLogError(SCENET, ERROR_NET_ADHOC_SOCKET_ALERTED, "socket alerted"); + } + // Connected Socket if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) { + hleEatMicro(1000); // There are two ways to flush, you can either set TCP_NODELAY to 1 or TCP_CORK to 0. // Apply Send Timeout Settings to Socket setSockTimeout(ptpsocket.id, SO_SNDTIMEO, timeout); int error = FlushPtpSocket(ptpsocket.id); - if (error == EAGAIN || error == EWOULDBLOCK || error == ETIMEDOUT) { + if (error == EAGAIN || error == EWOULDBLOCK) { // Non-Blocking if (nonblock) - return ERROR_NET_ADHOC_WOULD_BLOCK; + return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PTP_FLUSH, id, nullptr, nullptr, timeout, nullptr, nullptr, "ptp flush"); } + else if (isDisconnected(error)) { + // Change Socket State + ptpsocket.state = ADHOC_PTP_STATE_CLOSED; + + // Disconnected + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); + } + + if (error != 0) + DEBUG_LOG(SCENET, "sceNetAdhocPtpFlush[%i:%u -> %s:%u]: Error:%i", id, ptpsocket.lport, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport, error); } // Dummy Result, Always success? @@ -3835,10 +4059,10 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) { } // Invalid Socket - return ERROR_NET_ADHOC_INVALID_SOCKET_ID; + return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } // Library uninitialized - return ERROR_NET_ADHOC_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } /** @@ -3863,6 +4087,7 @@ static int sceNetAdhocGameModeCreateMaster(u32 dataAddr, int size) { if (size < 0 || !Memory::IsValidAddress(dataAddr)) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + hleEatMicro(1000); SceNetEtherAddr localMac; getLocalMac(&localMac); u8* buf = (u8*)realloc(gameModeBuffer, size); @@ -3872,13 +4097,21 @@ static int sceNetAdhocGameModeCreateMaster(u32 dataAddr, int size) { u8* data = (u8*)malloc(size); if (data) { Memory::Memcpy(data, dataAddr, size); - masterGameModeArea = { 0, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 1, localMac, data }; + masterGameModeArea = { 0, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 1, 0, localMac, data }; // Socket's buffer size should fit the largest size from master/replicas, should we waited until master & all replicas to be created first before creating the socket? (ie. the first time UpdateMaster being called?) gameModeSocket = sceNetAdhocPdpCreate((const char*)&localMac, ADHOC_GAMEMODE_PORT, size, 0); StartGameModeScheduler(size); - } - return 0; // returned an id just like CreateReplica? always return 0? + // Block current thread to sync initial master data + if (gameModeSocket > 0 && replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + auto sock = adhocSockets[gameModeSocket - 1]; + __KernelWaitCurThread(WAITTYPE_NET, sock->data.pdp.id, 0, 0, false, "syncing master data"); + DEBUG_LOG(SCENET, "GameMode: Blocking Thread %d to Sync initial Master data", __KernelGetCurThread()); + } + return hleLogDebug(SCENET, 0, "success"); // returned an id just like CreateReplica? always return 0? + } + + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); } /** @@ -3904,6 +4137,7 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s if (mac == nullptr || size < 0 || !Memory::IsValidAddress(dataAddr)) return hleLogError(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); + hleEatMicro(1000); int maxid = 0; auto it = std::find_if(replicaGameModeAreas.begin(), replicaGameModeAreas.end(), [mac, &maxid](GameModeArea const& e) { @@ -3913,7 +4147,7 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s // MAC address already existed! if (it != replicaGameModeAreas.end()) { WARN_LOG(SCENET, "sceNetAdhocGameModeCreateReplica - [%s] is already existed (id: %d)", mac2str((SceNetEtherAddr*)mac).c_str(), it->id); - return it->id; + return it->id; // ERROR_NET_ADHOC_ALREADY_CREATED } int ret = 0; @@ -3921,11 +4155,20 @@ static int sceNetAdhocGameModeCreateReplica(const char *mac, u32 dataAddr, int s if (data) { Memory::Memcpy(data, dataAddr, size); //int sock = sceNetAdhocPdpCreate(mac, ADHOC_GAMEMODE_PORT, size, 0); - GameModeArea gma = { maxid + 1, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 0, *(SceNetEtherAddr*)mac, data }; + GameModeArea gma = { maxid + 1, size, dataAddr, CoreTiming::GetGlobalTimeUsScaled(), 0, 0, *(SceNetEtherAddr*)mac, data }; replicaGameModeAreas.push_back(gma); - ret = gma.id; + ret = gma.id; // Valid id for replica is higher than 0? + + // Block current thread to sync initial master data + if (gameModeSocket > 0 && replicaGameModeAreas.size() == (gameModeMacs.size() - 1)) { + auto sock = adhocSockets[gameModeSocket - 1]; + __KernelWaitCurThread(WAITTYPE_NET, sock->data.pdp.id, ret, 0, false, "syncing master data"); + DEBUG_LOG(SCENET, "GameMode: Blocking Thread %d to Sync initial Master data", __KernelGetCurThread()); + } + return hleLogSuccessInfoI(SCENET, ret, "success"); } - return hleLogSuccessInfoI(SCENET, ret, "success"); // valid id for replica started from 1? + + return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); } static int sceNetAdhocGameModeUpdateMaster() { @@ -3943,28 +4186,36 @@ static int sceNetAdhocGameModeUpdateMaster() { Memory::Memcpy(masterGameModeArea.data, masterGameModeArea.addr, masterGameModeArea.size); masterGameModeArea.dataUpdated = 1; masterGameModeArea.updateTimestamp = CoreTiming::GetGlobalTimeUsScaled(); + // Reset sent marker + for (auto& gma : replicaGameModeAreas) + gma.dataSent = 0; + } + + hleEatMicro(1000); + return 0; +} + +int NetAdhocGameMode_DeleteMaster() { + if (masterGameModeArea.data) { + free(masterGameModeArea.data); + } + //NetAdhocPdp_Delete(masterGameModeArea.socket, 0); + masterGameModeArea = { 0 }; + + if (replicaGameModeAreas.size() <= 0) { + NetAdhocPdp_Delete(gameModeSocket, 0); + gameModeSocket = (int)INVALID_SOCKET; } return 0; } static int sceNetAdhocGameModeDeleteMaster() { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteMaster()"); + WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteMaster() at %08x", currentMIPS->pc); if (isZeroMAC(&masterGameModeArea.mac)) return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CREATED, "not created"); - if (masterGameModeArea.data) { - free(masterGameModeArea.data); - } - //sceNetAdhocPdpDelete(masterGameModeArea.socket, 0); - masterGameModeArea = { 0 }; - - if (replicaGameModeAreas.size() <= 0) { - sceNetAdhocPdpDelete(gameModeSocket, 0); - gameModeSocket = (int)INVALID_SOCKET; - } - - return 0; + return NetAdhocGameMode_DeleteMaster(); } static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { @@ -3992,10 +4243,10 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { GameModeUpdateInfo* gmuinfo = (GameModeUpdateInfo*)Memory::GetPointer(infoAddr); gmuinfo->length = sizeof(GameModeUpdateInfo); if (gma.data && gma.dataUpdated) { - memcpy(Memory::GetPointer(gma.addr), gma.data, gma.size); + Memory::Memcpy(gma.addr, gma.data, gma.size); gma.dataUpdated = 0; gmuinfo->updated = 1; - gmuinfo->timeStamp = std::max(gma.updateTimestamp, CoreTiming::GetGlobalTimeUsScaled() - 1000); + gmuinfo->timeStamp = std::max(gma.updateTimestamp, CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta); } else { gmuinfo->updated = 0; @@ -4005,11 +4256,12 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) { } } + hleEatMicro(1000); return 0; } static int sceNetAdhocGameModeDeleteReplica(int id) { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteReplica(%i)", id); + WARN_LOG(SCENET, "UNTESTED sceNetAdhocGameModeDeleteReplica(%i) at %08x", id, currentMIPS->pc); auto it = std::find_if(replicaGameModeAreas.begin(), replicaGameModeAreas.end(), [id](GameModeArea const& e) { return e.id == id; @@ -4072,6 +4324,12 @@ int NetAdhocMatching_Stop(int matchingId) { } matchingThreads[item->matching_thid] = 0; + // Make sure nobody locking/using the socket + item->socketlock->lock(); + // Delete the socket + NetAdhocPdp_Delete(item->socket, 0); // item->connected = (sceNetAdhocPdpDelete(item->socket, 0) < 0); + item->socketlock->unlock(); + // Multithreading Lock peerlock.lock(); @@ -4122,11 +4380,7 @@ int NetAdhocMatching_Delete(int matchingId) { // Delete the Fake PSP Thread //__KernelDeleteThread(item->matching_thid, SCE_KERNEL_ERROR_THREAD_TERMINATED, "AdhocMatching deleted"); //delete item->matchingThread; - // Make sure nobody locking/using the socket - item->socketlock->lock(); - // Delete the socket - NetAdhocPdp_Delete(item->socket, 0); // item->connected = (sceNetAdhocPdpDelete(item->socket, 0) < 0); - item->socketlock->unlock(); + // Free allocated memories free(item->hello); free(item->rxbuf); @@ -4146,12 +4400,7 @@ int NetAdhocMatching_Delete(int matchingId) { item = NULL; // Making sure there are no leftover matching events from this session which could cause a crash on the next session - for (auto it = matchingEvents.begin(); it != matchingEvents.end(); ) { - if (it->data[0] == matchingId) - it = matchingEvents.erase(it); - else - ++it; - } + deleteMatchingEvents(matchingId); // Stop Search break; @@ -4190,7 +4439,7 @@ int sceNetAdhocMatchingInit(u32 memsize) { fakePoolSize = memsize; // Initialize Library - matchingEvents.clear(); + deleteMatchingEvents(); netAdhocMatchingInited = true; // Return Success @@ -4248,7 +4497,8 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle SceNetAdhocMatchingContext * item = contexts; for (; item != NULL; item = item->next) { // Port Match found - if (item->port == port) return ERROR_NET_ADHOC_MATCHING_PORT_IN_USE; + if (item->port == port) + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_PORT_IN_USE, "adhoc matching port in use"); } // Allocate Context Memory @@ -4259,95 +4509,85 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle // Create PDP Socket SceNetEtherAddr localmac; getLocalMac(&localmac); - int socket = sceNetAdhocPdpCreate((const char*)&localmac, (uint32_t)port, rxbuflen, 0); - // Created PDP Socket - if (socket > 0) { + + // Clear Memory + memset(context, 0, sizeof(SceNetAdhocMatchingContext)); + + // Allocate Receive Buffer + context->rxbuf = (uint8_t*)malloc(rxbuflen); + + // Allocated Memory + if (context->rxbuf != NULL) { // Clear Memory - memset(context, 0, sizeof(SceNetAdhocMatchingContext)); + memset(context->rxbuf, 0, rxbuflen); - // Allocate Receive Buffer - context->rxbuf = (uint8_t *)malloc(rxbuflen); + // Fill in Context Data + context->id = findFreeMatchingID(); + context->mode = mode; + context->maxpeers = maxnum; + context->port = port; + context->rxbuflen = rxbuflen; + context->resendcounter = init_count; + context->resend_int = rexmt_int; // used as ack timeout on lost packet (ie. not receiving anything after sending)? + context->hello_int = hello_int; // client might set this to 0 + if (keepalive_int < 1) context->keepalive_int = PSP_ADHOCCTL_PING_TIMEOUT; else context->keepalive_int = keepalive_int; // client might set this to 0 + context->keepalivecounter = init_count; // used to multiply keepalive_int as timeout + context->timeout = (((u64)(keepalive_int)+(u64)rexmt_int) * (u64)init_count); + context->timeout += adhocDefaultTimeout; // For internet play we need higher timeout than what the game wanted + context->handler = handler; - // Allocated Memory - if (context->rxbuf != NULL) { - // Clear Memory - memset(context->rxbuf, 0, rxbuflen); + // Fill in Selfpeer + context->mac = localmac; - // Fill in Context Data - context->id = findFreeMatchingID(); - context->mode = mode; - context->maxpeers = maxnum; - context->port = port; - context->socket = socket; - context->rxbuflen = rxbuflen; - context->resendcounter = init_count; - context->resend_int = rexmt_int; // used as ack timeout on lost packet (ie. not receiving anything after sending)? - context->hello_int = hello_int; // client might set this to 0 - if (keepalive_int < 1) context->keepalive_int = PSP_ADHOCCTL_PING_TIMEOUT; else context->keepalive_int = keepalive_int; // client might set this to 0 - context->keepalivecounter = init_count; // used to multiply keepalive_int as timeout - context->timeout = (((u64)(keepalive_int) + (u64)rexmt_int) * (u64)init_count); - context->timeout += adhocDefaultTimeout; // For internet play we need higher timeout than what the game wanted - context->handler = handler; + // Create locks + context->socketlock = new std::recursive_mutex; + context->eventlock = new std::recursive_mutex; + context->inputlock = new std::recursive_mutex; - // Fill in Selfpeer - context->mac = localmac; + // Multithreading Lock + peerlock.lock(); //contextlock.lock(); - // Create locks - context->socketlock = new std::recursive_mutex; - context->eventlock = new std::recursive_mutex; - context->inputlock = new std::recursive_mutex; + // Add Callback Handler + context->handler.entryPoint = callbackAddr; + context->matching_thid = static_cast(matchingThreads.size()); + matchingThreads.push_back(0); - // Multithreading Lock - peerlock.lock(); //contextlock.lock(); - - // Add Callback Handler - context->handler.entryPoint = callbackAddr; - context->matching_thid = static_cast(matchingThreads.size()); - matchingThreads.push_back(0); + // Link Context + //context->connected = true; + context->next = contexts; + contexts = context; - // Link Context - //context->connected = true; - context->next = contexts; - contexts = context; + // Multithreading UnLock + peerlock.unlock(); //contextlock.unlock(); - // Multithreading UnLock - peerlock.unlock(); //contextlock.unlock(); + // Just to make sure Adhoc is already connected + //hleDelayResult(context->id, "give time to init/cleanup", adhocEventDelayMS * 1000); - // Just to make sure Adhoc is already connected - //hleDelayResult(context->id, "give time to init/cleanup", adhocEventDelayMS * 1000); - - // Return Matching ID - return context->id; - } - - // Close PDP Socket - sceNetAdhocPdpDelete(socket, 0); // context->connected = (sceNetAdhocPdpDelete(socket, 0) < 0); + // Return Matching ID + return hleLogDebug(SCENET, context->id, "success"); } // Free Memory free(context); - - // Port in use - if (socket < 1) return ERROR_NET_ADHOC_MATCHING_PORT_IN_USE; // ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED; // -1; // ERROR_NET_ADHOC_MATCHING_NOT_ESTABLISHED; } // Out of Memory - return ERROR_NET_ADHOC_MATCHING_NO_SPACE; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NO_SPACE, "adhoc matching no space"); } // InvalidERROR_NET_Arguments - return ERROR_NET_ADHOC_MATCHING_INVALID_ARG; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_INVALID_ARG, "adhoc matching invalid arg"); } // Invalid Receive Buffer Size - return ERROR_NET_ADHOC_MATCHING_RXBUF_TOO_SHORT; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_RXBUF_TOO_SHORT, "adhoc matching rxbuf too short"); } // Invalid Member Limit - return ERROR_NET_ADHOC_MATCHING_INVALID_MAXNUM; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_INVALID_MAXNUM, "adhoc matching invalid maxnum"); } // Uninitialized Library - return ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED; + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhoc matching not initialized"); } int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int evthStack, int inthPri, int inthPartitionId, int inthStack, int optLen, u32 optDataAddr) { @@ -4371,18 +4611,11 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int } //else return ERROR_NET_ADHOC_MATCHING_INVALID_ARG; // ERROR_NET_ADHOC_MATCHING_INVALID_OPTLEN; // Returning Not Success will cause GTA:VC stuck unable to choose host/join menu - //Add your own MAC as a member (only if it's empty?) - /*SceNetAdhocMatchingMemberInternal * peer = addMember(item, &item->mac); - switch (item->mode) { - case PSP_ADHOC_MATCHING_MODE_PARENT: - peer->state = PSP_ADHOC_MATCHING_PEER_OFFER; - break; - case PSP_ADHOC_MATCHING_MODE_CHILD: - peer->state = PSP_ADHOC_MATCHING_PEER_CHILD; - break; - case PSP_ADHOC_MATCHING_MODE_P2P: - peer->state = PSP_ADHOC_MATCHING_PEER_P2P; - }*/ + // Create PDP Socket + int sock = sceNetAdhocPdpCreate((const char*)&item->mac, static_cast(item->port), item->rxbuflen, 0); + item->socket = sock; + if (sock < 1) + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_PORT_IN_USE, "adhoc matching port in use"); // Create & Start the Fake PSP Thread ("matching_ev%d" and "matching_io%d") netAdhocValidateLoopMemory(); @@ -4591,23 +4824,19 @@ static int sceNetAdhocMatchingSelectTarget(int matchingId, const char *macAddres return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhocmatching not initialized"); } -int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { - WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTargetWithOpt(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, currentMIPS->pc); - if (!g_Config.bEnableWlan) - return -1; - +int NetAdhocMatching_CancelTargetWithOpt(int matchingId, const char* macAddress, int optLen, u32 optDataPtr) { // Initialized Library if (netAdhocMatchingInited) { - SceNetEtherAddr * target = (SceNetEtherAddr *)macAddress; - void * opt = NULL; + SceNetEtherAddr* target = (SceNetEtherAddr*)macAddress; + void* opt = NULL; if (Memory::IsValidAddress(optDataPtr)) opt = Memory::GetPointer(optDataPtr); // Valid Arguments if (target != NULL && ((optLen == 0) || (optLen > 0 && opt != NULL))) { // Find Matching Context - SceNetAdhocMatchingContext * context = findMatchingContext(matchingId); + SceNetAdhocMatchingContext* context = findMatchingContext(matchingId); // Found Matching Context if (context != NULL) @@ -4616,7 +4845,7 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres if (context->running) { // Find Peer - SceNetAdhocMatchingMemberInternal * peer = findPeer(context, (SceNetEtherAddr *)target); + SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)target); // Found Peer if (peer != NULL) @@ -4645,6 +4874,7 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres // Marking peer to be timedout instead of deleting immediately peer->lastping = 0; + hleEatCycles(adhocDefaultDelay); // Return Success return 0; } @@ -4672,11 +4902,18 @@ int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddres return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_INITIALIZED, "adhocmatching not initialized"); } +int sceNetAdhocMatchingCancelTargetWithOpt(int matchingId, const char *macAddress, int optLen, u32 optDataPtr) { + WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTargetWithOpt(%i, %s, %i, %08x) at %08x", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str(), optLen, optDataPtr, currentMIPS->pc); + if (!g_Config.bEnableWlan) + return -1; + return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, optLen, optDataPtr); +} + int sceNetAdhocMatchingCancelTarget(int matchingId, const char *macAddress) { WARN_LOG(SCENET, "UNTESTED sceNetAdhocMatchingCancelTarget(%i, %s)", matchingId, mac2str((SceNetEtherAddr*)macAddress).c_str()); if (!g_Config.bEnableWlan) return -1; - return sceNetAdhocMatchingCancelTargetWithOpt(matchingId, macAddress, 0, 0); + return NetAdhocMatching_CancelTargetWithOpt(matchingId, macAddress, 0, 0); } int sceNetAdhocMatchingGetHelloOpt(int matchingId, u32 optLenAddr, u32 optDataAddr) { @@ -5014,65 +5251,31 @@ int sceNetAdhocMatchingSendData(int matchingId, const char *mac, int dataLen, u3 void* data = NULL; if (Memory::IsValidAddress(dataAddr)) data = Memory::GetPointer(dataAddr); - // FIXME: If the target MAC is 00:00:00:00:00:00 (invalid mac) Should we default to P2P/Parent's MAC or return an Error? - if (isZeroMAC((const SceNetEtherAddr*)mac)) { - int sent = 0; - peerlock.lock(); - // Iterate Peer List for Matching Target - SceNetAdhocMatchingMemberInternal* peer = context->peerlist; - for (; peer != NULL; peer = peer->next) + // Find Target Peer + SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)mac); + + // Found Peer + if (peer != NULL) + { + // Valid Peer Connection State + if (peer->state == PSP_ADHOC_MATCHING_PEER_PARENT || peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_P2P) { - // Valid Peer Connection State - if (peer->state == PSP_ADHOC_MATCHING_PEER_PARENT || peer->state == PSP_ADHOC_MATCHING_PEER_P2P) - { - // Skip Busy peers - if (peer->sending) - continue; + // Send in Progress + if (peer->sending) + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_DATA_BUSY, "data busy"); - // Mark Peer as Sending - peer->sending = 1; + // Mark Peer as Sending + peer->sending = 1; - // Send Data to Peer - sendBulkData(context, peer, dataLen, data); - sent++; - break; - } + // Send Data to Peer + sendBulkData(context, peer, dataLen, data); + + // Return Success + return 0; } - peerlock.unlock(); - if (sent == 0) - return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_UNKNOWN_TARGET, "invalid target"); - - // Return Success - return 0; - } - else { - // Find Target Peer - SceNetAdhocMatchingMemberInternal* peer = findPeer(context, (SceNetEtherAddr*)mac); - - // Found Peer - if (peer != NULL) - { - // Valid Peer Connection State - if (peer->state == PSP_ADHOC_MATCHING_PEER_PARENT || peer->state == PSP_ADHOC_MATCHING_PEER_CHILD || peer->state == PSP_ADHOC_MATCHING_PEER_P2P) - { - // Send in Progress - if (peer->sending) - return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_DATA_BUSY, "data busy"); - - // Mark Peer as Sending - peer->sending = 1; - - // Send Data to Peer - sendBulkData(context, peer, dataLen, data); - - // Return Success - return 0; - } - - // Not connected / accepted - return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_ESTABLISHED, "not established"); - } + // Not connected / accepted + return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_NOT_ESTABLISHED, "not established"); } // Peer not found @@ -5200,7 +5403,7 @@ void __NetTriggerCallbacks() { std::lock_guard adhocGuard(adhocEvtMtx); hleSkipDeadbeef(); - int delayus = 10000; + int delayus = adhocDefaultDelay; auto params = adhocctlEvents.begin(); if (params != adhocctlEvents.end()) @@ -5211,10 +5414,10 @@ void __NetTriggerCallbacks() u32_le args[3] = { 0, 0, 0 }; args[0] = flags; args[1] = error; + u64 now = (u64)(time_now_d() * 1000000.0); // FIXME: When Joining a group, Do we need to wait for group creator's peer data before triggering the callback to make sure the game not to thinks we're the group creator? - u64 now = (u64)(time_now_d() * 1000000.0); - if ((flags != ADHOCCTL_EVENT_CONNECT && flags != ADHOCCTL_EVENT_GAME) || adhocConnectionType != ADHOC_JOIN || getActivePeerCount() > 0 || now - adhocctlStartTime > adhocDefaultTimeout) + if ((flags != ADHOCCTL_EVENT_CONNECT && flags != ADHOCCTL_EVENT_GAME) || adhocConnectionType != ADHOC_JOIN || getActivePeerCount() > 0 || static_cast(now - adhocctlStartTime) > adhocDefaultTimeout) { // Since 0 is a valid index to types_ we use -1 to detects if it was loaded from an old save state if (actionAfterAdhocMipsCall < 0) { @@ -5225,11 +5428,14 @@ void __NetTriggerCallbacks() switch (flags) { case ADHOCCTL_EVENT_CONNECT: newState = ADHOCCTL_STATE_CONNECTED; - if (adhocConnectionType != ADHOC_JOIN) + if (adhocConnectionType == ADHOC_CREATE) delayus = adhocEventDelay; // May affects Dissidia 012 and GTA VCS + else if (adhocConnectionType == ADHOC_CONNECT) + delayus = adhocEventDelay / 2; break; case ADHOCCTL_EVENT_SCAN: // notified only when scan completed? newState = ADHOCCTL_STATE_DISCONNECTED; + //delayus = adhocEventDelay / 2; break; case ADHOCCTL_EVENT_DISCONNECT: newState = ADHOCCTL_STATE_DISCONNECTED; @@ -5237,8 +5443,11 @@ void __NetTriggerCallbacks() case ADHOCCTL_EVENT_GAME: { newState = ADHOCCTL_STATE_GAMEMODE; - if (adhocConnectionType != ADHOC_JOIN) - delayus = adhocEventDelay; + delayus = adhocEventDelay; + // TODO: Use blocking PTP connection to sync the timing just like official prx did (which is done before notifying user-defined Adhocctl Handlers) + // Workaround: Extra delay to prevent Joining player to progress faster than the Creator on Pocket Pool, but unbalanced delays could cause an issue on Shaun White Snowboarding :( + if (adhocConnectionType == ADHOC_JOIN) + delayus += adhocExtraDelay * 3; // Shows player list INFO_LOG(SCENET, "GameMode - All players have joined:"); int i = 0; @@ -5256,7 +5465,7 @@ void __NetTriggerCallbacks() newState = ADHOCCTL_STATE_WOL; break; case ADHOCCTL_EVENT_ERROR: - newState = ADHOCCTL_STATE_DISCONNECTED; + delayus = adhocDefaultDelay * 3; break; } @@ -5275,13 +5484,13 @@ void __NetTriggerCallbacks() } // Must be delayed long enough whenever there is a pending callback. Should it be 100-500ms for Adhocctl Events? or Not Less than the delays on sceNetAdhocctl HLE? - sceKernelDelayThread(delayus); + sceKernelDelayThread(adhocDefaultDelay); } void __NetMatchingCallbacks() //(int matchingId) { std::lock_guard adhocGuard(adhocEvtMtx); - int delayus = 10000; + int delayus = adhocDefaultDelay; auto params = matchingEvents.begin(); if (params != matchingEvents.end()) @@ -5360,8 +5569,10 @@ const HLEFunction sceNetAdhocMatching[] = { }; int NetAdhocctl_ExitGameMode() { - if (gameModeSocket > 0) - sceNetAdhocPdpDelete(gameModeSocket, 0); + if (gameModeSocket > 0) { + NetAdhocPdp_Delete(gameModeSocket, 0); + gameModeSocket = (int)INVALID_SOCKET; + } deleteAllGMB(); @@ -5395,6 +5606,7 @@ static int sceNetAdhocctlGetGameModeInfo(u32 infoAddr) { break; } + hleEatMicro(1000); return 0; } @@ -5413,6 +5625,8 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) { if (netAdhocctlInited) { // Minimum Arguments if (buflen != NULL) { + // FIXME: Sometimes returing 0x80410682 when Adhocctl is still BUSY or before AdhocctlGetState became ADHOCCTL_STATE_CONNECTED or related to Auth/Library ? + // Multithreading Lock peerlock.lock(); @@ -5431,6 +5645,8 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) { // Calculate Request Count int requestcount = *buflen / sizeof(SceNetAdhocctlPeerInfoEmu); + // FIXME: When bufAddr is not null but buffer size is smaller than activePeers * sizeof(SceNetAdhocctlPeerInfoEmu), simply return buffer size = 0 without filling the buffer? + // Clear Memory memset(buf, 0, *buflen); @@ -5481,11 +5697,11 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) { } // Invalid Arguments - return ERROR_NET_ADHOCCTL_INVALID_ARG; + return hleLogDebug(SCENET, ERROR_NET_ADHOCCTL_INVALID_ARG, "invalid arg"); } // Uninitialized Library - return ERROR_NET_ADHOCCTL_NOT_INITIALIZED; + return hleLogDebug(SCENET, ERROR_NET_ADHOCCTL_NOT_INITIALIZED, "not initialized"); } static int sceNetAdhocctlGetAddrByName(const char *nickName, u32 sizeAddr, u32 bufAddr) { @@ -5544,7 +5760,7 @@ static int sceNetAdhocctlGetAddrByName(const char *nickName, u32 sizeAddr, u32 b getLocalMac(&mac); buf[discovered].mac_addr = mac; buf[discovered].flags = 0x0400; - u64 lastrecv = CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta; + u64 lastrecv = std::max(0LL, static_cast(CoreTiming::GetGlobalTimeUsScaled() - defaultLastRecvDelta)); buf[discovered++].last_recv = lastrecv; DEBUG_LOG(SCENET, "Peer [%s][%s][%s][%llu]", mac2str(&mac).c_str(), inet_ntoa(addr.sin_addr), nickName, lastrecv); @@ -6752,6 +6968,7 @@ int matchingEventThread(int matchingId) context->eventlock->lock(); // Iterate Message List + int msg_count = 0; ThreadMessage * msg = context->event_stack; for (; msg != NULL; msg = msg->next) { @@ -6769,6 +6986,7 @@ int matchingEventThread(int matchingId) // Notify Event Handlers notifyMatchingHandler(context, msg, opt, bufAddr, bufLen, args); //context->eventlock->lock(); + msg_count++; } // Clear Event Message Stack @@ -6776,6 +6994,7 @@ int matchingEventThread(int matchingId) // Free Stack context->eventlock->unlock(); + INFO_LOG(SCENET, "EventLoop[%d]: Finished (%d msg)", matchingId, msg_count); } // Free memory @@ -6842,7 +7061,7 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi { // Hello Message Broadcast necessary because of Hello Interval if (context->hello_int > 0) - if ((now - lasthello) >= context->hello_int) + if (static_cast(now - lasthello) >= static_cast(context->hello_int)) { // Broadcast Hello Message broadcastHelloMessage(context); @@ -6854,7 +7073,7 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi // Ping Required if (context->keepalive_int > 0) - if ((now - lastping) >= context->keepalive_int) + if (static_cast(now - lastping) >= static_cast(context->keepalive_int)) { // Broadcast Ping Message broadcastPingMessage(context); @@ -6935,9 +7154,9 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi SceNetAdhocctlPeerInfo* peer = findFriend(&sendermac); if (peer != NULL) { now = CoreTiming::GetGlobalTimeUsScaled(); - u64_le delta = now - peer->last_recv; - DEBUG_LOG(SCENET, "Timestamp Delta: %llu (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); - if (peer->last_recv != 0) peer->last_recv = now - defaultLastRecvDelta; + s64 delta = now - peer->last_recv; + DEBUG_LOG(SCENET, "Timestamp LastRecv Delta: %lld (%llu - %llu) from %s", delta, now, peer->last_recv, mac2str(&sendermac).c_str()); + if (peer->last_recv != 0) peer->last_recv = std::max(peer->last_recv, now - defaultLastRecvDelta); } else { WARN_LOG(SCENET, "InputLoop[%d]: Unknown Peer[%s:%u] (Recved=%i, Length=%i)", matchingId, mac2str(&sendermac).c_str(), senderport, recvresult, rxbuflen); @@ -6988,10 +7207,60 @@ int matchingInputThread(int matchingId) // TODO: The MatchingInput thread is usi } if (contexts != NULL) { + // Process Last Messages + if (context->input_stack != NULL) + { + // Claim Stack + context->inputlock->lock(); + + // Iterate Message List + int msg_count = 0; + ThreadMessage* msg = context->input_stack; + for (; msg != NULL; msg = msg->next) + { + // Default Optional Data + void* opt = NULL; + + // Grab Optional Data + if (msg->optlen > 0) opt = ((u8*)msg) + sizeof(ThreadMessage); + + // Send Accept Packet + if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_ACCEPT) sendAcceptPacket(context, &msg->mac, msg->optlen, opt); + + // Send Join Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_JOIN) sendJoinPacket(context, &msg->mac, msg->optlen, opt); + + // Send Cancel Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_CANCEL) sendCancelPacket(context, &msg->mac, msg->optlen, opt); + + // Send Bulk Data Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK) sendBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Send Birth Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_BIRTH) sendBirthPacket(context, &msg->mac); + + // Send Death Packet + else if (msg->opcode == PSP_ADHOC_MATCHING_PACKET_DEATH) sendDeathPacket(context, &msg->mac); + + // Cancel Bulk Data Transfer (does nothing as of now as we fire and forget anyway) // Do we need to check DeathPacket and ByePacket here? + //else if(msg->opcode == PSP_ADHOC_MATCHING_PACKET_BULK_ABORT) sendAbortBulkDataPacket(context, &msg->mac, msg->optlen, opt); + + // Pop input stack from front (this should be queue instead of stack?) + context->input_stack = msg->next; + free(msg); + msg = context->input_stack; + msg_count++; + } + + // Free Stack + context->inputlock->unlock(); + INFO_LOG(SCENET, "InputLoop[%d]: Finished (%d msg)", matchingId, msg_count); + } + // Clear IO Message Stack clearStack(context, PSP_ADHOC_MATCHING_INPUT_STACK); - // Send Bye Messages + // Send Bye Messages. FIXME: Official prx seems to be sending DEATH instead of BYE packet during MatchingStop? But DEATH packet doesn't works with DBZ Team Tag sendByePacket(context); // Free Peer List Buffer diff --git a/Core/HLE/sceNetAdhoc.h b/Core/HLE/sceNetAdhoc.h index 682a609b2f..73016cc20d 100644 --- a/Core/HLE/sceNetAdhoc.h +++ b/Core/HLE/sceNetAdhoc.h @@ -98,6 +98,8 @@ int NetAdhoc_Term(); extern bool netAdhocInited; extern bool netAdhocctlInited; extern bool networkInited; +extern bool netAdhocGameModeEntered; +extern int netAdhocEnterGameModeTimeout; extern int adhocDefaultTimeout; //3000000 usec extern int adhocDefaultDelay; //10000 extern int adhocExtraDelay; //20000 diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp index 0a601db8d4..51bb3beedc 100644 --- a/Core/HLE/scePsmf.cpp +++ b/Core/HLE/scePsmf.cpp @@ -29,6 +29,7 @@ #include "Core/HLE/sceMpeg.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/HW/MediaEngine.h" +#include "Core/CoreTiming.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" @@ -59,6 +60,8 @@ static int videoPixelMode = GE_CMODE_32BIT_ABGR8888; static int videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP; static int psmfPlayerLibVersion = 0; +int eventPsmfPlayerStatusChange = -1; + enum PsmfPlayerError { ERROR_PSMF_NOT_INITIALIZED = 0x80615001, ERROR_PSMF_BAD_VERSION = 0x80615002, @@ -666,10 +669,20 @@ static PsmfPlayer *getPsmfPlayer(u32 psmfplayer) return 0; } +static void __PsmfPlayerStatusChange(u64 userdata, int cyclesLate) { + PsmfPlayerStatus status = PsmfPlayerStatus(userdata & 0xFFFFFFFF); + u32 psmfPlayer = userdata >> 32; + PsmfPlayer *player = getPsmfPlayer(psmfPlayer); + if (player) { + player->status = status; + } +} + void __PsmfInit() { videoPixelMode = GE_CMODE_32BIT_ABGR8888; videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP; psmfPlayerLibVersion = 0; + eventPsmfPlayerStatusChange = CoreTiming::RegisterEvent("PsmfPlayerStatusChange", &__PsmfPlayerStatusChange); } void __PsmfPlayerLoadModule(int devkitVersion) { @@ -685,18 +698,26 @@ void __PsmfDoState(PointerWrap &p) { } void __PsmfPlayerDoState(PointerWrap &p) { - auto s = p.Section("scePsmfPlayer", 1, 2); + auto s = p.Section("scePsmfPlayer", 1, 3); if (!s) return; Do(p, psmfPlayerMap); Do(p, videoPixelMode); Do(p, videoLoopStatus); - if (s >= 2) { - Do(p, psmfPlayerLibVersion); + if (s < 3) { + eventPsmfPlayerStatusChange = -1; } else { + Do(p, eventPsmfPlayerStatusChange); + if (eventPsmfPlayerStatusChange != -1) { + CoreTiming::RestoreRegisterEvent(eventPsmfPlayerStatusChange, "PsmfPlayerStatusChangeEvent", &__PsmfPlayerStatusChange); + } + } + if (s < 2) { // Assume the latest, which is what we were emulating before. psmfPlayerLibVersion = 0x06060010; + } else { + Do(p, psmfPlayerLibVersion); } } @@ -709,6 +730,13 @@ void __PsmfShutdown() { psmfPlayerMap.clear(); } +static void DelayPsmfStateChange(u32 psmfPlayer, u32 newState, s64 delayUs) { + if (eventPsmfPlayerStatusChange == -1) { + eventPsmfPlayerStatusChange = CoreTiming::RegisterEvent("PsmfPlayerStatusChange", &__PsmfPlayerStatusChange); + } + CoreTiming::ScheduleEvent(usToCycles(delayUs), eventPsmfPlayerStatusChange, (u64)psmfPlayer << 32 | newState); +} + static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfData) || !Memory::IsValidAddress(psmfData)) { // Crashes on a PSP. @@ -1082,38 +1110,32 @@ static u32 scePsmfGetEPidWithTimestamp(u32 psmfStruct, u32 ts) return epid; } -static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) -{ +static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) { auto player = PSPPointer::Create(psmfPlayer); const auto data = PSPPointer::Create(dataPtr); if (!player.IsValid() || !data.IsValid()) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): bad pointers", psmfPlayer, dataPtr); // Crashes on a PSP. - return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS; + return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad pointers"); } if (!data->buffer.IsValid()) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): invalid buffer address %08x", psmfPlayer, dataPtr, data->buffer.ptr); // Also crashes on a PSP. *player = 0; - return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS; + return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "invalid buffer address %08x", data->buffer.ptr); } if (data->bufferSize < 0x00285800) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): buffer too small %08x", psmfPlayer, dataPtr, data->bufferSize); *player = 0; - return ERROR_PSMFPLAYER_BUFFER_SIZE; + return hleReportError(ME, ERROR_PSMFPLAYER_BUFFER_SIZE, "buffer too small %08x", data->bufferSize); } if (data->threadPriority < 0x10 || data->threadPriority >= 0x6E) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): bad thread priority %02x", psmfPlayer, dataPtr, data->threadPriority); *player = 0; - return ERROR_PSMFPLAYER_INVALID_PARAM; + return hleReportError(ME, ERROR_PSMFPLAYER_INVALID_PARAM, "bad thread priority %02x", data->threadPriority); } if (!psmfPlayerMap.empty()) { *player = 0; return hleReportError(ME, ERROR_MPEG_ALREADY_INIT, "already have an active player"); } - INFO_LOG(ME, "scePsmfPlayerCreate(%08x, %08x)", psmfPlayer, dataPtr); PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer); if (!psmfplayer) { psmfplayer = new PsmfPlayer(data); @@ -1129,39 +1151,35 @@ static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) videoPixelMode = GE_CMODE_32BIT_ABGR8888; videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP; - psmfplayer->status = PSMF_PLAYER_STATUS_INIT; - return hleDelayResult(0, "player create", 20000); + int delayUs = 20000; + DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_INIT, delayUs); + return hleLogSuccessInfoI(ME, hleDelayResult(0, "player create", delayUs)); } static int scePsmfPlayerStop(u32 psmfPlayer) { PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer); if (!psmfplayer) { - ERROR_LOG(ME, "scePsmfPlayerStop(%08x): invalid psmf player", psmfPlayer); - return ERROR_PSMFPLAYER_INVALID_STATUS; + return hleLogError(ME, ERROR_PSMFPLAYER_INVALID_STATUS, "invalid psmf player"); } if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) { - ERROR_LOG(ME, "scePsmfPlayerStop(%08x): not yet playing", psmfPlayer); - return ERROR_PSMFPLAYER_INVALID_STATUS; + return hleLogError(ME, ERROR_PSMFPLAYER_INVALID_STATUS, "not yet playing"); } psmfplayer->AbortFinish(); - INFO_LOG(ME, "scePsmfPlayerStop(%08x)", psmfPlayer); - psmfplayer->status = PSMF_PLAYER_STATUS_STANDBY; - return hleDelayResult(0, "psmfplayer stop", 3000); + int delayUs = 3000; + DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_STANDBY, delayUs); + return hleLogSuccessInfoI(ME, hleDelayResult(0, "psmfplayer stop", delayUs)); } -static int scePsmfPlayerBreak(u32 psmfPlayer) -{ - WARN_LOG(ME, "scePsmfPlayerBreak(%08x)", psmfPlayer); +static int scePsmfPlayerBreak(u32 psmfPlayer) { PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer); if (!psmfplayer) { - ERROR_LOG(ME, "scePsmfPlayerBreak(%08x): invalid psmf player", psmfPlayer); - return ERROR_PSMFPLAYER_INVALID_STATUS; + return hleLogError(ME, ERROR_PSMFPLAYER_INVALID_STATUS, "invalid psmf player", psmfPlayer); } psmfplayer->AbortFinish(); - return 0; + return hleLogWarning(ME, 0); } static int _PsmfPlayerFillRingbuffer(PsmfPlayer *psmfplayer) { @@ -1200,17 +1218,17 @@ static int _PsmfPlayerFillRingbuffer(PsmfPlayer *psmfplayer) { static int _PsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int offset, bool docallback) { PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer); if (!psmfplayer || psmfplayer->status != PSMF_PLAYER_STATUS_INIT) { - return ERROR_PSMFPLAYER_INVALID_STATUS; + return hleReportError(ME, ERROR_PSMFPLAYER_INVALID_STATUS, "invalid psmf player or status"); } if (!filename) { - return ERROR_PSMFPLAYER_INVALID_PARAM; + return hleLogError(ME, ERROR_PSMFPLAYER_INVALID_PARAM, "invalid filename"); } int delayUs = 1100; psmfplayer->filehandle = pspFileSystem.OpenFile(filename, (FileAccess) FILEACCESS_READ); if (psmfplayer->filehandle < 0) { - return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs); + return hleLogError(ME, hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs), "invalid file data or does not exist"); } if (offset != 0) @@ -1223,15 +1241,14 @@ static int _PsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int of const u32 magic = *(u32_le *)buf; if (magic != PSMF_MAGIC) { // TODO: Let's keep trying as we were before. - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: incorrect PSMF magic, bad data"); - //return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT; + ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: incorrect PSMF magic (%08x), bad data", magic); + //return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "incorrect PSMF magic (%08x), bad data", magic); } // TODO: Merge better with Psmf. u16 numStreams = *(u16_be *)(buf + 0x80); if (numStreams > 128) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: too many streams in PSMF video, bogus data"); - return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs); + return hleReportError(ME, hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs), "too many streams in PSMF video, bogus data"); } psmfplayer->totalVideoStreams = 0; @@ -1274,83 +1291,34 @@ static int _PsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int of _PsmfPlayerFillRingbuffer(psmfplayer); psmfplayer->totalDurationTimestamp = psmfplayer->mediaengine->getLastTimeStamp(); - - psmfplayer->status = PSMF_PLAYER_STATUS_STANDBY; - - return hleDelayResult(0, "psmfplayer set", delayUs); + DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_STANDBY, delayUs); + return hleLogSuccessInfoI(ME, hleDelayResult(0, "psmfplayer set", delayUs)); } -static int scePsmfPlayerSetPsmf(u32 psmfPlayer, const char *filename) -{ - u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, false); - if (result == ERROR_PSMFPLAYER_INVALID_STATUS) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid psmf player or status", psmfPlayer, filename); - } else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid filename", psmfPlayer, filename); - } else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid file data or does not exist", psmfPlayer, filename); - } else { - INFO_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s)", psmfPlayer, filename); - } - return result; +static int scePsmfPlayerSetPsmf(u32 psmfPlayer, const char *filename) { + return _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, false); } -static int scePsmfPlayerSetPsmfCB(u32 psmfPlayer, const char *filename) -{ +static int scePsmfPlayerSetPsmfCB(u32 psmfPlayer, const char *filename) { // TODO: hleCheckCurrentCallbacks? - u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, true); - if (result == ERROR_PSMFPLAYER_INVALID_STATUS) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid psmf player or status", psmfPlayer, filename); - } else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid filename", psmfPlayer, filename); - } else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid file data or does not exist", psmfPlayer, filename); - } else { - INFO_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s)", psmfPlayer, filename); - } - return result; + return _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, true); } -static int scePsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int offset) -{ - u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, false); - if (result == ERROR_PSMFPLAYER_INVALID_STATUS) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid psmf player or status", psmfPlayer, filename); - } else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid filename", psmfPlayer, filename); - } else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid file data or does not exist", psmfPlayer, filename); - } else { - INFO_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s)", psmfPlayer, filename); - } - return result; +static int scePsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int offset) { + return _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, false); } -static int scePsmfPlayerSetPsmfOffsetCB(u32 psmfPlayer, const char *filename, int offset) -{ +static int scePsmfPlayerSetPsmfOffsetCB(u32 psmfPlayer, const char *filename, int offset) { // TODO: hleCheckCurrentCallbacks? - u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, true); - if (result == ERROR_PSMFPLAYER_INVALID_STATUS) { - ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid psmf player or status", psmfPlayer, filename); - } else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid filename", psmfPlayer, filename); - } else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) { - ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid file data or does not exist", psmfPlayer, filename); - } else { - INFO_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s)", psmfPlayer, filename); - } - return result; + return _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, true); } -static int scePsmfPlayerGetAudioOutSize(u32 psmfPlayer) -{ +static int scePsmfPlayerGetAudioOutSize(u32 psmfPlayer) { PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer); if (!psmfplayer) { - ERROR_LOG(ME, "scePsmfPlayerGetAudioOutSize(%08x): invalid psmf player", psmfPlayer); - return ERROR_PSMFPLAYER_INVALID_STATUS; + return hleLogError(ME, ERROR_PSMFPLAYER_INVALID_STATUS, "invalid psmf player"); } - WARN_LOG(ME, "%i = scePsmfPlayerGetAudioOutSize(%08x)", audioSamplesBytes, psmfPlayer); - return audioSamplesBytes; + return hleLogWarning(ME, audioSamplesBytes); } static bool __PsmfPlayerContinueSeek(PsmfPlayer *psmfplayer, int tries = 50) { @@ -1460,7 +1428,10 @@ static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts) // Does not alter current pts, it just catches up when Update()/etc. get there. int delayUs = psmfplayer->status == PSMF_PLAYER_STATUS_PLAYING ? 3000 : 0; - psmfplayer->status = PSMF_PLAYER_STATUS_PLAYING; + if (delayUs == 0) + psmfplayer->status = PSMF_PLAYER_STATUS_PLAYING; + else + DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_PLAYING, delayUs); psmfplayer->warmUp = 0; psmfplayer->mediaengine->openContext(); diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index 5cb209c6c4..51c44b72ae 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -515,17 +515,21 @@ bool getUMDReplacePermit() { static u32 sceUmdReplaceProhibit() { - UMDReplacePermit = false; DEBUG_LOG(SCEIO,"sceUmdReplaceProhibit()"); - host->UpdateUI(); + if (UMDReplacePermit) { + UMDReplacePermit = false; + host->NotifySwitchUMDUpdated(); + } return 0; } static u32 sceUmdReplacePermit() { - UMDReplacePermit = true; DEBUG_LOG(SCEIO,"sceUmdReplacePermit()"); - host->UpdateUI(); + if (!UMDReplacePermit) { + UMDReplacePermit = true; + host->NotifySwitchUMDUpdated(); + } return 0; } diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 7793e372a0..c6dccd6796 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -137,6 +137,11 @@ static int sceUsbCamReadMicBlocking(u32 bufAddr, u32 size) { return __MicInputBlocking(size >> 1, config->micParam.frequency, bufAddr); } +static int sceUsbCamReadMic(u32 bufAddr, u32 size) { + INFO_LOG(HLE, "UNIMPL sceUsbCamReadMic: size: %d", size); + return __MicInputBlocking(size >> 1, config->micParam.frequency, bufAddr); +} + static int sceUsbCamSetupVideo(u32 paramAddr, u32 workareaAddr, int wasize) { if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupVideoParam))) { Memory::ReadStruct(paramAddr, &config->videoParam); @@ -185,7 +190,7 @@ static int sceUsbCamReadVideoFrameBlocking(u32 bufAddr, u32 size) { if (Memory::IsValidRange(bufAddr, size)) { Memory::Memcpy(bufAddr, videoBuffer, transferSize); } - return videoBufferLength; + return transferSize; } static int sceUsbCamReadVideoFrame(u32 bufAddr, u32 size) { @@ -194,7 +199,7 @@ static int sceUsbCamReadVideoFrame(u32 bufAddr, u32 size) { if (Memory::IsValidRange(bufAddr, size)) { Memory::Memcpy(bufAddr, videoBuffer, transferSize); } - nextVideoFrame = videoBufferLength; + nextVideoFrame = transferSize; return 0; } @@ -243,7 +248,7 @@ const HLEFunction sceUsbCam[] = { 0X82A64030, &WrapI_V, "sceUsbCamStartMic", 'i', "" }, { 0X5145868A, &WrapI_V, "sceUsbCamStopMic", 'i', "" }, { 0X36636925, &WrapI_UU, "sceUsbCamReadMicBlocking", 'i', "xx" }, - { 0X3DC0088E, nullptr, "sceUsbCamReadMic", '?', "" }, + { 0X3DC0088E, &WrapI_UU, "sceUsbCamReadMic", 'i', "xx" }, { 0XB048A67D, nullptr, "sceUsbCamWaitReadMicEnd", '?', "" }, { 0XF8847F60, nullptr, "sceUsbCamPollReadMicEnd", '?', "" }, { 0X5778B452, nullptr, "sceUsbCamGetMicDataLength", '?', "" }, @@ -301,7 +306,7 @@ void Register_sceUsbCam() } std::vector Camera::getDeviceList() { - #ifdef HAVE_WIN32_CAMERA + #ifdef HAVE_WIN32_CAMERA if (winCamera) { return winCamera->getDeviceList(); } @@ -321,7 +326,7 @@ int Camera::startCapture() { INFO_LOG(HLE, "%s resolution: %dx%d", __FUNCTION__, width, height); config->mode = Camera::Mode::Video; - #ifdef HAVE_WIN32_CAMERA + #ifdef HAVE_WIN32_CAMERA if (winCamera) { if (winCamera->isShutDown()) { delete winCamera; @@ -345,7 +350,7 @@ int Camera::startCapture() { int Camera::stopCapture() { INFO_LOG(HLE, "%s", __FUNCTION__); - #ifdef HAVE_WIN32_CAMERA + #ifdef HAVE_WIN32_CAMERA if (winCamera) { winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); } diff --git a/Core/HLE/sceUsbGps.cpp b/Core/HLE/sceUsbGps.cpp index 723e83d445..456d09b487 100644 --- a/Core/HLE/sceUsbGps.cpp +++ b/Core/HLE/sceUsbGps.cpp @@ -92,14 +92,16 @@ const HLEFunction sceUsbGps[] = {0X268F95CA, nullptr, "sceUsbGpsSetInitDataLocation", '?', "" }, {0X31F95CDE, nullptr, "sceUsbGpsGetPowerSaveMode", '?', "" }, {0X54D26AA4, &WrapI_U, "sceUsbGpsGetInitDataLocation", 'i', "x" }, + {0X5881C826, nullptr, "sceUsbGpsGetStaticNavMode", '?', "" }, {0X63D1F89D, nullptr, "sceUsbGpsResetInitialPosition", '?', "" }, {0X69E4AAA8, nullptr, "sceUsbGpsSaveInitData", '?', "" }, {0X6EED4811, &WrapI_V, "sceUsbGpsClose", 'i', "" }, - {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x"}, + {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x" }, {0X934EC2B2, &WrapI_UU, "sceUsbGpsGetData", 'i', "xx" }, {0X9D8F99E8, nullptr, "sceUsbGpsSetPowerSaveMode", '?', "" }, {0X9F267D34, &WrapI_V, "sceUsbGpsOpen", 'i', "" }, {0XA259CD67, nullptr, "sceUsbGpsReset", '?', "" }, + {0XA8ED0BC2, nullptr, "sceUsbGpsSetStaticNavMode", '?', "" }, }; void Register_sceUsbGps() diff --git a/Core/HLE/sceUsbGps.h b/Core/HLE/sceUsbGps.h index 24c2f5ccd0..39513cda71 100644 --- a/Core/HLE/sceUsbGps.h +++ b/Core/HLE/sceUsbGps.h @@ -43,7 +43,7 @@ typedef struct { float garbage3; float speed; float bearing; -} GpsData; +} GpsData; typedef struct { unsigned char id; diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp index 91855a0804..246d8921e1 100644 --- a/Core/HLE/sceUsbMic.cpp +++ b/Core/HLE/sceUsbMic.cpp @@ -19,6 +19,7 @@ #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" +#include "Common/System/System.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceKernelThread.h" @@ -74,6 +75,8 @@ static void __UsbMicAudioUpdate(u64 userdata, int cyclesLate) { isNeedInput = false; } else { u64 waitTimeus = (waitingThread.needSize - Microphone::availableAudioBufSize()) * 1000000 / 2 / waitingThread.sampleRate; + if(eventUsbMicAudioUpdate == -1) + eventUsbMicAudioUpdate = CoreTiming::RegisterEvent("UsbMicAudioUpdate", &__UsbMicAudioUpdate); CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventUsbMicAudioUpdate, userdata); } } else { @@ -118,7 +121,7 @@ void __UsbMicShutdown() { } void __UsbMicDoState(PointerWrap &p) { - auto s = p.Section("sceUsbMic", 0, 1); + auto s = p.Section("sceUsbMic", 0, 2); if (!s) { return; } @@ -128,18 +131,25 @@ void __UsbMicDoState(PointerWrap &p) { Do(p, curSampleRate); Do(p, curChannels); Do(p, micState); - // Maybe also need to save the state of audioBuf. - if (waitingThreads.size() != 0 && p.mode == PointerWrap::MODE_READ) { - u64 waitTimeus = (waitingThreads[0].needSize - Microphone::availableAudioBufSize()) * 1000000 / 2 / waitingThreads[0].sampleRate; - CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventUsbMicAudioUpdate, waitingThreads[0].threadID); + if (s > 1) { + Do(p, eventUsbMicAudioUpdate); + if (eventUsbMicAudioUpdate != -1) { + CoreTiming::RestoreRegisterEvent(eventUsbMicAudioUpdate, "UsbMicAudioUpdate", &__UsbMicAudioUpdate); + } + } else { + eventUsbMicAudioUpdate = -1; } + + if (!audioBuf && numNeedSamples > 0) { + audioBuf = new QueueBuf(numNeedSamples << 1); + } + if (micState == 0) { if (Microphone::isMicStarted()) Microphone::stopMic(); } else if (micState == 1) { if (Microphone::isMicStarted()) { - Microphone::stopMic(); - Microphone::startMic(new std::vector({ curSampleRate, curChannels })); + // Ok, started. } else { Microphone::startMic(new std::vector({ curSampleRate, curChannels })); } @@ -302,6 +312,12 @@ int Microphone::startMic(void *param) { #ifdef HAVE_WIN32_MICROPHONE if (winMic) winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::START, param }); +#elif PPSSPP_PLATFORM(ANDROID) + std::vector *micParam = static_cast*>(param); + int sampleRate = micParam->at(0); + int channels = micParam->at(1); + INFO_LOG(HLE, "microphone_command : sr = %d", sampleRate); + System_SendMessage("microphone_command", ("startRecording:" + std::to_string(sampleRate)).c_str()); #endif micState = 1; return 0; @@ -311,6 +327,8 @@ int Microphone::stopMic() { #ifdef HAVE_WIN32_MICROPHONE if (winMic) winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); +#elif PPSSPP_PLATFORM(ANDROID) + System_SendMessage("microphone_command", "stopRecording"); #endif micState = 0; return 0; @@ -319,6 +337,8 @@ int Microphone::stopMic() { bool Microphone::isHaveDevice() { #ifdef HAVE_WIN32_MICROPHONE return winMic->getDeviceCounts() >= 1; +#elif PPSSPP_PLATFORM(ANDROID) + return audioRecording_Available(); #endif return false; } @@ -327,6 +347,8 @@ bool Microphone::isMicStarted() { #ifdef HAVE_WIN32_MICROPHONE if(winMic) return winMic->isStarted(); +#elif PPSSPP_PLATFORM(ANDROID) + return audioRecording_State(); #endif return false; } @@ -403,6 +425,8 @@ u32 __MicInputBlocking(u32 maxSamples, u32 sampleRate, u32 bufAddr) { waitTimeus = (size - Microphone::availableAudioBufSize()) * 1000000 / 2 / sampleRate; isNeedInput = true; } + if(eventUsbMicAudioUpdate == -1) + eventUsbMicAudioUpdate = CoreTiming::RegisterEvent("UsbMicAudioUpdate", &__UsbMicAudioUpdate); CoreTiming::ScheduleEvent(usToCycles(waitTimeus), eventUsbMicAudioUpdate, __KernelGetCurThread()); MicWaitInfo waitInfo = { __KernelGetCurThread(), bufAddr, size, sampleRate }; std::unique_lock lock(wtMutex); @@ -419,7 +443,7 @@ const HLEFunction sceUsbMic[] = {0x06128E42, &WrapI_V, "sceUsbMicPollInputEnd", 'i', "" }, {0x2E6DCDCD, &WrapI_UUU, "sceUsbMicInputBlocking", 'i', "xxx" }, {0x45310F07, &WrapI_U, "sceUsbMicInputInitEx", 'i', "x" }, - {0x5F7F368D, &WrapI_V , "sceUsbMicInput", 'i', "" }, + {0x5F7F368D, &WrapI_V, "sceUsbMicInput", 'i', "" }, {0x63400E20, &WrapI_V, "sceUsbMicGetInputLength", 'i', "" }, {0xB8E536EB, &WrapI_III, "sceUsbMicInputInit", 'i', "iii" }, {0xF899001C, &WrapI_V, "sceUsbMicWaitInputEnd", 'i', "" }, diff --git a/Core/HW/MediaEngine.cpp b/Core/HW/MediaEngine.cpp index 2d97916349..13f4b4a1ec 100644 --- a/Core/HW/MediaEngine.cpp +++ b/Core/HW/MediaEngine.cpp @@ -145,6 +145,8 @@ MediaEngine::MediaEngine(): m_pdata(0) { m_videoStream = -1; m_audioStream = -1; + m_expectedVideoStreams = 0; + m_desWidth = 0; m_desHeight = 0; m_decodingsize = 0; @@ -182,7 +184,7 @@ void MediaEngine::closeMedia() { } void MediaEngine::DoState(PointerWrap &p) { - auto s = p.Section("MediaEngine", 1, 5); + auto s = p.Section("MediaEngine", 1, 6); if (!s) return; @@ -200,6 +202,11 @@ void MediaEngine::DoState(PointerWrap &p) { } else { m_mpegheaderReadPos = m_mpegheaderSize; } + if (s >= 6) { + Do(p, m_expectedVideoStreams); + } else { + m_expectedVideoStreams = 0; + } Do(p, m_ringbuffersize); @@ -271,20 +278,22 @@ bool MediaEngine::SetupStreams() { } // Looking good. Let's add those streams. - const AVCodec *h264_codec = avcodec_find_decoder(AV_CODEC_ID_H264); + int videoStreamNum = -1; for (int i = 0; i < numStreams; i++) { const u8 *const currentStreamAddr = m_mpegheader + 0x82 + i * 16; int streamId = currentStreamAddr[0]; // We only set video streams. We demux the audio stream separately. if ((streamId & PSMF_VIDEO_STREAM_ID) == PSMF_VIDEO_STREAM_ID) { - AVStream *stream = avformat_new_stream(m_pFormatCtx, h264_codec); - stream->id = 0x00000100 | streamId; - stream->request_probe = 0; - stream->need_parsing = AVSTREAM_PARSE_FULL; - // We could set the width here, but we don't need to. + ++videoStreamNum; + addVideoStream(videoStreamNum, streamId); } } + // Add the streams to meet the expectation. + for (int i = videoStreamNum + 1; i < m_expectedVideoStreams; i++) { + addVideoStream(i); + } + #endif return true; @@ -403,6 +412,38 @@ bool MediaEngine::reloadStream() return loadStream(m_mpegheader, 2048, m_ringbuffersize); } +bool MediaEngine::addVideoStream(int streamNum, int streamId) { +#ifdef USE_FFMPEG + if (m_pFormatCtx) { + // no need to add an existing stream. + if ((u32)streamNum < m_pFormatCtx->nb_streams) + return true; + const AVCodec *h264_codec = avcodec_find_decoder(AV_CODEC_ID_H264); + if (!h264_codec) + return false; + AVStream *stream = avformat_new_stream(m_pFormatCtx, h264_codec); + if (stream) { + // Reference ISO/IEC 13818-1. + if (streamId == -1) + streamId = PSMF_VIDEO_STREAM_ID | streamNum; + + stream->id = 0x00000100 | streamId; + stream->request_probe = 0; + stream->need_parsing = AVSTREAM_PARSE_FULL; + // We could set the width here, but we don't need to. + if (streamNum >= m_expectedVideoStreams) { + ++m_expectedVideoStreams; + } + return true; + } + } +#endif + if (streamNum >= m_expectedVideoStreams) { + ++m_expectedVideoStreams; + } + return false; +} + int MediaEngine::addStreamData(const u8 *buffer, int addSize) { int size = addSize; if (size > 0 && m_pdata) { diff --git a/Core/HW/MediaEngine.h b/Core/HW/MediaEngine.h index b684aedd7f..af81ae7447 100644 --- a/Core/HW/MediaEngine.h +++ b/Core/HW/MediaEngine.h @@ -61,6 +61,7 @@ public: void closeMedia(); bool loadStream(const u8 *buffer, int readSize, int RingbufferSize); bool reloadStream(); + bool addVideoStream(int streamNum, int streamId = -1); // open the mpeg context bool openContext(bool keepReadPos = false); void closeContext(); @@ -116,6 +117,7 @@ public: // TODO: Very little of this below should be public. int m_sws_fmt; u8 *m_buffer; int m_videoStream; + int m_expectedVideoStreams; // Used by the demuxer. int m_audioStream; diff --git a/Core/HW/MemoryStick.cpp b/Core/HW/MemoryStick.cpp index da0058261f..444334a3e3 100644 --- a/Core/HW/MemoryStick.cpp +++ b/Core/HW/MemoryStick.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/CoreTiming.h" @@ -28,12 +29,13 @@ static MemStickState memStickState; static MemStickFatState memStickFatState; static bool memStickNeedsAssign = false; static u64 memStickInsertedAt = 0; +static uint64_t memstickInitialFree = 0; const u64 normalMemstickSize = 9ULL * 1024 * 1024 * 1024; const u64 smallMemstickSize = 1ULL * 1024 * 1024 * 1024; void MemoryStick_DoState(PointerWrap &p) { - auto s = p.Section("MemoryStick", 1, 4); + auto s = p.Section("MemoryStick", 1, 5); if (!s) return; @@ -46,6 +48,9 @@ void MemoryStick_DoState(PointerWrap &p) { u64 memStickSize = normalMemstickSize; Do(p, memStickSize); } + if (s >= 5) { + Do(p, memstickInitialFree); + } if (s >= 3) { Do(p, memStickNeedsAssign); @@ -71,18 +76,28 @@ u64 MemoryStick_SectorSize() { } u64 MemoryStick_FreeSpace() { - // Cap the memory stick size to avoid math errors when old games get sizes that were - // hard to imagine back then. - // We have a compat setting to make it even smaller for Harry Potter : Goblet of Fire, see #13266. - const u64 memStickSize = PSP_CoreParameter().compat.flags().ReportSmallMemstick ? smallMemstickSize : (u64)g_Config.iMemStickSizeGB * 1024 * 1024 * 1024; - u64 usedSpace = pspFileSystem.getDirSize("ms0:/PSP/SAVEDATA/");// Assume the memory stick is only used to store savedata. - u64 freeSpace; - if (usedSpace >= memStickSize) - freeSpace = 0; - else - freeSpace = memStickSize - usedSpace; + const CompatFlags &flags = PSP_CoreParameter().compat.flags(); + u64 realFreeSpace = pspFileSystem.FreeSpace("ms0:/"); - return freeSpace; + // Cap the memory stick size to avoid math errors when old games get sizes that were + // not planned for back then (even though 2GB cards were available.) + // We have a compat setting to make it even smaller for Harry Potter : Goblet of Fire, see #13266. + const u64 memStickSize = flags.ReportSmallMemstick ? smallMemstickSize : (u64)g_Config.iMemStickSizeGB * 1024 * 1024 * 1024; + // Assume the memory stick is only used to store savedata. + u64 usedSpace = pspFileSystem.getDirSize("ms0:/PSP/SAVEDATA/"); + u64 simulatedFreeSpace = 0; + if (usedSpace < memStickSize) { + simulatedFreeSpace = memStickSize - usedSpace; + } + if (flags.MemstickFixedFree) { + // Assassin's Creed: Bloodlines fails to save if free space changes incorrectly during game. + realFreeSpace = 0; + if (usedSpace <= memstickInitialFree) { + realFreeSpace = memstickInitialFree - usedSpace; + } + } + + return std::min(simulatedFreeSpace, realFreeSpace); } void MemoryStick_SetFatState(MemStickFatState state) { @@ -116,4 +131,5 @@ void MemoryStick_Init() { } memStickNeedsAssign = false; + memstickInitialFree = pspFileSystem.FreeSpace("ms0:/") + pspFileSystem.getDirSize("ms0:/PSP/SAVEDATA/"); } diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index f588539414..6a0a87d3af 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -331,15 +331,7 @@ void ADSREnvelope::SetSimpleEnvelope(u32 ADSREnv1, u32 ADSREnv2) { } } -SasInstance::SasInstance() - : maxVoices(PSP_SAS_VOICES_MAX), - sampleRate(44100), - outputMode(PSP_SAS_OUTPUTMODE_MIXED), - mixBuffer(0), - sendBuffer(0), - sendBufferDownsampled(0), - sendBufferProcessed(0), - grainSize(0) { +SasInstance::SasInstance() { #ifdef AUDIO_TO_FILE audioDump = fopen("D:\\audio.raw", "wb"); #endif diff --git a/Core/HW/SasAudio.h b/Core/HW/SasAudio.h index 480250dc70..de883afa36 100644 --- a/Core/HW/SasAudio.h +++ b/Core/HW/SasAudio.h @@ -282,16 +282,16 @@ public: int GetGrainSize() const { return grainSize; } int EstimateMixUs(); - int maxVoices; - int sampleRate; - int outputMode; + int maxVoices = PSP_SAS_VOICES_MAX; + int sampleRate = 44100; + int outputMode = PSP_SAS_OUTPUTMODE_MIXED; - int *mixBuffer; - int *sendBuffer; - s16 *sendBufferDownsampled; - s16 *sendBufferProcessed; + int *mixBuffer = nullptr; + int *sendBuffer = nullptr; + s16 *sendBufferDownsampled = nullptr; + s16 *sendBufferProcessed = nullptr; - FILE *audioDump; + FILE *audioDump = nullptr; void Mix(u32 outAddr, u32 inAddr = 0, int leftVol = 0, int rightVol = 0); void MixVoice(SasVoice &voice); @@ -310,6 +310,6 @@ public: private: SasReverb reverb_; - int grainSize; + int grainSize = 0; int16_t mixTemp_[PSP_SAS_MAX_GRAIN * 4 + 2 + 8]; // some extra margin for very high pitches. }; diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 84e18df67e..bc4ea5d310 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -396,7 +396,7 @@ u32 AuCtx::AuSetLoopNum(int loop) // return 1 to read more data stream, 0 don't read int AuCtx::AuCheckStreamDataNeeded() { // If we would ask for bytes, then some are needed. - if (AuStreamBytesNeeded() != 0) { + if (AuStreamBytesNeeded() > 0) { return 1; } return 0; diff --git a/Core/Host.h b/Core/Host.h index 35838c8083..4a4c739c3d 100644 --- a/Core/Host.h +++ b/Core/Host.h @@ -57,6 +57,8 @@ public: virtual void NotifyUserMessage(const std::string &message, float duration = 1.0f, u32 color = 0x00FFFFFF, const char *id = nullptr) {} virtual void SendUIMessage(const std::string &message, const std::string &value) {} + virtual void NotifySwitchUMDUpdated() {} + // Used for headless. virtual bool ShouldSkipUI() { return false; } virtual void SendDebugOutput(const std::string &output) {} diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp index 3d49b2dc61..96c8f7133b 100644 --- a/Core/MIPS/ARM/ArmCompLoadStore.cpp +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -386,37 +386,19 @@ namespace MIPSComp } void ArmJit::Comp_Cache(MIPSOpcode op) { - // int imm = (s16)(op & 0xFFFF); - // int rs = _RS; - // int addr = R(rs) + imm; + CONDITIONAL_DISABLE(LSU); + int func = (op >> 16) & 0x1F; - // It appears that a cache line is 0x40 (64) bytes, loops in games - // issue the cache instruction at that interval. - - // These codes might be PSP-specific, they don't match regular MIPS cache codes very well + // See Int_Cache for the definitions. switch (func) { - // Icache - case 8: - // Invalidate the instruction cache at this address - DISABLE; - break; - // Dcache - case 24: - // "Create Dirty Exclusive" - for avoiding a cacheline fill before writing to it. - // Will cause garbage on the real machine so we just ignore it, the app will overwrite the cacheline. - break; - case 25: // Hit Invalidate - zaps the line if present in cache. Should not writeback???? scary. - // No need to do anything. - break; - case 27: // D-cube. Hit Writeback Invalidate. Tony Hawk Underground 2 - break; - case 30: // GTA LCS, a lot. Fill (prefetch). Tony Hawk Underground 2 - break; - + case 24: break; + case 25: break; + case 27: break; + case 30: break; default: + // Fall back to the interpreter. DISABLE; - break; } } } diff --git a/Core/MIPS/ARM64/Arm64Asm.cpp b/Core/MIPS/ARM64/Arm64Asm.cpp index 99f4dadf65..b95b8e54b4 100644 --- a/Core/MIPS/ARM64/Arm64Asm.cpp +++ b/Core/MIPS/ARM64/Arm64Asm.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "ppsspp_config.h" + #if PPSSPP_ARCH(ARM64) #include "Common/Log.h" diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index ad706c5221..92b241d17d 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -311,6 +311,7 @@ namespace MIPSComp { case 40: //sb case 41: //sh case 43: //sw +#ifndef MASKED_PSP_MEMORY if (jo.cachePointers && g_Config.bFastMemory) { // ARM has smaller load/store immediate displacements than MIPS, 12 bits - and some memory ops only have 8 bits. int offsetRange = 0x3ff; @@ -327,6 +328,7 @@ namespace MIPSComp { gpr.MapReg(rt, load ? MAP_NOINIT : 0); targetReg = gpr.R(rt); } + switch (o) { case 35: LDR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; case 37: LDRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; @@ -342,6 +344,7 @@ namespace MIPSComp { break; } } +#endif if (!load && gpr.IsImm(rt) && gpr.TryMapTempImm(rt) != INVALID_REG) { // We're storing an immediate value, let's see if we can optimize rt. @@ -432,37 +435,19 @@ namespace MIPSComp { } void Arm64Jit::Comp_Cache(MIPSOpcode op) { - // int imm = (s16)(op & 0xFFFF); - // int rs = _RS; - // int addr = R(rs) + imm; + CONDITIONAL_DISABLE(LSU); + int func = (op >> 16) & 0x1F; - // It appears that a cache line is 0x40 (64) bytes, loops in games - // issue the cache instruction at that interval. - - // These codes might be PSP-specific, they don't match regular MIPS cache codes very well + // See Int_Cache for the definitions. switch (func) { - // Icache - case 8: - // Invalidate the instruction cache at this address - DISABLE; - break; - // Dcache - case 24: - // "Create Dirty Exclusive" - for avoiding a cacheline fill before writing to it. - // Will cause garbage on the real machine so we just ignore it, the app will overwrite the cacheline. - break; - case 25: // Hit Invalidate - zaps the line if present in cache. Should not writeback???? scary. - // No need to do anything. - break; - case 27: // D-cube. Hit Writeback Invalidate. Tony Hawk Underground 2 - break; - case 30: // GTA LCS, a lot. Fill (prefetch). Tony Hawk Underground 2 - break; - + case 24: break; + case 25: break; + case 27: break; + case 30: break; default: + // Fall back to the interpreter. DISABLE; - break; } } } diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 6b6e26eca7..d90ed9bce8 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -358,7 +358,6 @@ namespace MIPSComp { ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); } } - fp.STP(32, INDEX_SIGNED, fpr.V(vregs[0]), fpr.V(vregs[1]), SCRATCH1_64, 0); fp.STP(32, INDEX_SIGNED, fpr.V(vregs[2]), fpr.V(vregs[3]), SCRATCH1_64, 8); diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index cfdb3d0385..6a518cc976 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -100,6 +100,10 @@ Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), f jo.enablePointerify = false; } +#ifdef MASKED_PSP_MEMORY + jo.enablePointerify = false; +#endif + logBlocks = 0; dontLogBlocks = 0; blocks.Init(); diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index a8ed9d8166..ec2fc33aaf 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -108,37 +108,17 @@ namespace MIPSComp { void IRFrontend::Comp_Cache(MIPSOpcode op) { CONDITIONAL_DISABLE(LSU); -// int imm = (s16)(op & 0xFFFF); -// int rs = _RS; -// int addr = R(rs) + imm; int func = (op >> 16) & 0x1F; - // It appears that a cache line is 0x40 (64) bytes, loops in games - // issue the cache instruction at that interval. - - // These codes might be PSP-specific, they don't match regular MIPS cache codes very well + // See Int_Cache for the definitions. switch (func) { - // Icache - case 8: - // Invalidate the instruction cache at this address - DISABLE; - break; - // Dcache - case 24: - // "Create Dirty Exclusive" - for avoiding a cacheline fill before writing to it. - // Will cause garbage on the real machine so we just ignore it, the app will overwrite the cacheline. - break; - case 25: // Hit Invalidate - zaps the line if present in cache. Should not writeback???? scary. - // No need to do anything. - break; - case 27: // D-cube. Hit Writeback Invalidate. Tony Hawk Underground 2 - break; - case 30: // GTA LCS, a lot. Fill (prefetch). Tony Hawk Underground 2 - break; - + case 24: break; + case 25: break; + case 27: break; + case 30: break; default: + // Fall back to the interpreter. DISABLE; - break; } } } diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp index b49dab057f..be67cc915d 100644 --- a/Core/MIPS/MIPSInt.cpp +++ b/Core/MIPS/MIPSInt.cpp @@ -102,6 +102,8 @@ namespace MIPSInt // issue the cache instruction at that interval. // These codes might be PSP-specific, they don't match regular MIPS cache codes very well + + // NOTE: If you add support for more, make sure they are handled in the various Jit::Comp_Cache. switch (func) { // Icache case 8: diff --git a/Core/MIPS/x86/CompLoadStore.cpp b/Core/MIPS/x86/CompLoadStore.cpp index e3d87bcf94..f094684417 100644 --- a/Core/MIPS/x86/CompLoadStore.cpp +++ b/Core/MIPS/x86/CompLoadStore.cpp @@ -401,7 +401,20 @@ namespace MIPSComp { } void Jit::Comp_Cache(MIPSOpcode op) { - DISABLE; + CONDITIONAL_DISABLE(LSU); + + int func = (op >> 16) & 0x1F; + + // See Int_Cache for the definitions. + switch (func) { + case 24: break; + case 25: break; + case 27: break; + case 30: break; + default: + // Fall back to the interpreter. + DISABLE; + } } } diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 69c23c85e5..d027fdd191 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -121,7 +121,7 @@ static MemoryView views[] = static const int num_views = sizeof(views) / sizeof(MemoryView); inline static bool CanIgnoreView(const MemoryView &view) { -#if PPSSPP_ARCH(32BIT) +#ifdef MASKED_PSP_MEMORY // Basically, 32-bit platforms can ignore views that are masked out anyway. return (view.flags & MV_MIRROR_PREVIOUS) && (view.virtual_address & ~MEMVIEW32_MASK) != 0; #else diff --git a/Core/MemMap.h b/Core/MemMap.h index 7383b719ea..1bd6027c22 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -84,7 +84,7 @@ extern u32 g_PSPModel; // UWP has such limited memory management that we need to mask // even in 64-bit mode. Also, when using the sanitizer, we need to mask as well. -#if PPSSPP_ARCH(32BIT) || PPSSPP_PLATFORM(UWP) || USE_ADDRESS_SANITIZER +#if PPSSPP_ARCH(32BIT) || PPSSPP_PLATFORM(UWP) || USE_ADDRESS_SANITIZER || PPSSPP_PLATFORM(IOS) #define MASKED_PSP_MEMORY #endif @@ -261,10 +261,17 @@ inline bool IsVRAMAddress(const u32 address) { inline bool IsDepthTexVRAMAddress(const u32 address) { return ((address & 0x3FE00000) == 0x04200000) || ((address & 0x3FE00000) == 0x04600000); } + +// 0x08000000 -> 0x08800000 inline bool IsKernelAddress(const u32 address) { return ((address & 0x3F800000) == 0x08000000); } +// 0x08000000 -> 0x08400000 +inline bool IsKernelAndNotVolatileAddress(const u32 address) { + return ((address & 0x3FC00000) == 0x08000000); +} + bool IsScratchpadAddress(const u32 address); // Used for auto-converted char * parameters, which can sometimes legitimately be null - diff --git a/Core/Opcode.h b/Core/Opcode.h index 6eb01c41fc..456d77dbf6 100644 --- a/Core/Opcode.h +++ b/Core/Opcode.h @@ -17,7 +17,7 @@ #pragma once -#include "CommonTypes.h" +#include "Common/CommonTypes.h" // Broken out of MemMap.h to avoid a bad include dependency. diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp index 0e4066bdbf..bb8d109c10 100644 --- a/Core/Screenshot.cpp +++ b/Core/Screenshot.cpp @@ -18,12 +18,8 @@ #include "ppsspp_config.h" #include -#ifdef USING_QT_UI -#include -#else #include #include "ext/jpge/jpge.h" -#endif #include "Common/ColorConv.h" #include "Common/File/FileUtil.h" @@ -35,7 +31,6 @@ #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" -#ifndef USING_QT_UI // This is used to make non-ASCII paths work for filename. // Technically only needed on Windows. class JPEGFileStream : public jpge::output_stream @@ -118,7 +113,6 @@ static bool WriteScreenshotToPNG(png_imagep image, const char *filename, int con return false; } } -#endif static bool ConvertPixelTo8888RGBA(GPUDebugBufferFormat fmt, u8 &r, u8 &g, u8 &b, u8 &a, const void *buffer, int offset, bool rev) { const u8 *buf8 = (const u8 *)buffer; @@ -348,10 +342,6 @@ bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotTy } bool Save888RGBScreenshot(const char *filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h) { -#ifdef USING_QT_UI - QImage image(bufferRGB888, w, h, QImage::Format_RGB888); - return image.save(filename, fmt == ScreenshotFormat::PNG ? "PNG" : "JPG"); -#else if (fmt == ScreenshotFormat::PNG) { png_image png; memset(&png, 0, sizeof(png)); @@ -374,14 +364,9 @@ bool Save888RGBScreenshot(const char *filename, ScreenshotFormat fmt, const u8 * } else { return false; } -#endif } bool Save8888RGBAScreenshot(const char *filename, const u8 *buffer, int w, int h) { -#ifdef USING_QT_UI - QImage image(buffer, w, h, QImage::Format_RGBA8888); - return image.save(filename, "PNG"); -#else png_image png; memset(&png, 0, sizeof(png)); png.version = PNG_IMAGE_VERSION; @@ -396,5 +381,4 @@ bool Save8888RGBAScreenshot(const char *filename, const u8 *buffer, int w, int h success = false; } return success; -#endif } diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index 3c4b808502..2ea62371e8 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -26,6 +26,7 @@ #include +#include #include #include #include diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp index 23b662c672..11236f1f9a 100644 --- a/GPU/Common/DepalettizeShaderCommon.cpp +++ b/GPU/Common/DepalettizeShaderCommon.cpp @@ -38,6 +38,12 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang WRITE(p, "SamplerState texSamp : register(s0);\n"); WRITE(p, "Texture2D tex : register(t0);\n"); WRITE(p, "Texture2D pal : register(t3);\n"); + // Support for depth. + if (pixelFormat == GE_FORMAT_DEPTH16) { + WRITE(p, "cbuffer params : register(b0) {\n"); + WRITE(p, " float z_scale; float z_offset;\n"); + WRITE(p, "};\n"); + } } else if (language == GLSL_VULKAN) { WRITE(p, "#version 450\n"); WRITE(p, "#extension GL_ARB_separate_shader_objects : enable\n"); @@ -53,7 +59,6 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang WRITE(p, " float z_scale; float z_offset;\n"); WRITE(p, "};\n"); } - } else { if (gl_extensions.IsGLES) { WRITE(p, "#version 300 es\n"); @@ -159,7 +164,7 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang void GenerateDepalShaderFloat(char *buffer, GEBufferFormat pixelFormat, ShaderLanguage lang) { char *p = buffer; - const char *modFunc = lang == HLSL_DX9 ? "fmod" : "mod"; + const char *modFunc = lang == HLSL_D3D9 ? "fmod" : "mod"; char lookupMethod[128] = "index.r"; char offset[128] = ""; @@ -281,7 +286,7 @@ void GenerateDepalShaderFloat(char *buffer, GEBufferFormat pixelFormat, ShaderLa float texel_offset = ((float)clutBase + 0.5f) / texturePixels; sprintf(offset, " + %f", texel_offset); - if (lang == GLSL_140) { + if (lang == GLSL_1xx) { if (gl_extensions.IsGLES) { WRITE(p, "#version 100\n"); WRITE(p, "precision mediump float;\n"); @@ -300,7 +305,7 @@ void GenerateDepalShaderFloat(char *buffer, GEBufferFormat pixelFormat, ShaderLa WRITE(p, " float coord = (%s * %f)%s;\n", lookupMethod, index_multiplier, offset); WRITE(p, " gl_FragColor = texture2D(pal, vec2(coord, 0.0));\n"); WRITE(p, "}\n"); - } else if (lang == HLSL_DX9) { + } else if (lang == HLSL_D3D9) { WRITE(p, "sampler tex: register(s0);\n"); WRITE(p, "sampler pal: register(s1);\n"); WRITE(p, "float4 main(float2 v_texcoord0 : TEXCOORD0) : COLOR0 {\n"); @@ -313,18 +318,17 @@ void GenerateDepalShaderFloat(char *buffer, GEBufferFormat pixelFormat, ShaderLa void GenerateDepalShader(char *buffer, GEBufferFormat pixelFormat, ShaderLanguage language) { switch (language) { - case GLSL_140: + case GLSL_1xx: GenerateDepalShaderFloat(buffer, pixelFormat, language); break; - case GLSL_300: + case GLSL_3xx: case GLSL_VULKAN: case HLSL_D3D11: GenerateDepalShader300(buffer, pixelFormat, language); break; - case HLSL_DX9: + case HLSL_D3D9: GenerateDepalShaderFloat(buffer, pixelFormat, language); break; - case HLSL_D3D11_LEVEL9: default: _assert_msg_(false, "Depal shader language not supported: %d", (int)language); } diff --git a/GPU/Common/DepalettizeShaderCommon.h b/GPU/Common/DepalettizeShaderCommon.h index 3520fded93..75a46c67ad 100644 --- a/GPU/Common/DepalettizeShaderCommon.h +++ b/GPU/Common/DepalettizeShaderCommon.h @@ -18,8 +18,9 @@ #pragma once #include + +#include "Common/GPU/Shader.h" #include "GPU/ge_constants.h" -#include "GPU/Common/ShaderCommon.h" static const int DEPAL_TEXTURE_OLD_AGE = 120; @@ -32,4 +33,4 @@ public: protected: uint32_t GenerateShaderID(uint32_t clutMode, GEBufferFormat pixelFormat) const; uint32_t GetClutID(GEPaletteFormat clutFormat, uint32_t clutHash) const; -}; \ No newline at end of file +}; diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index bb554d1c38..1784813175 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -480,8 +480,9 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr, return GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_NRM_FLOAT | GE_VTYPE_POS_FLOAT | (vertType & (GE_VTYPE_IDX_MASK | GE_VTYPE_THROUGH)); } -bool DrawEngineCommon::ApplyShaderBlending() { +bool DrawEngineCommon::ApplyFramebufferRead(bool *fboTexNeedsBind) { if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { + *fboTexNeedsBind = false; return true; } @@ -502,7 +503,7 @@ bool DrawEngineCommon::ApplyShaderBlending() { return false; } - fboTexNeedBind_ = true; + *fboTexNeedsBind = true; gstate_c.Dirty(DIRTY_SHADERBLEND); return true; diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index f53b96f487..7a063a2d26 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -112,7 +112,7 @@ protected: // Vertex decoding void DecodeVertsStep(u8 *dest, int &i, int &decodedVerts); - bool ApplyShaderBlending(); + bool ApplyFramebufferRead(bool *fboTexNeedsBind); inline int IndexSize(u32 vtype) const { const u32 indexType = (vtype & GE_VTYPE_IDX_MASK); @@ -169,7 +169,7 @@ protected: GEPrimitiveType prevPrim_ = GE_PRIM_INVALID; // Shader blending state - bool fboTexNeedBind_ = false; + bool fboTexNeedsBind_ = false; bool fboTexBound_ = false; // Hardware tessellation diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp new file mode 100644 index 0000000000..3fe4764029 --- /dev/null +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -0,0 +1,1053 @@ +// Copyright (c) 2012- 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/. + +#include +#include + +#include "Common/Log.h" +#include "Common/StringUtils.h" +#include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/GPU/ShaderWriter.h" +#include "Core/Reporting.h" +#include "Core/Config.h" +#include "GPU/Common/GPUStateUtils.h" +#include "GPU/Common/ShaderId.h" +#include "GPU/Common/ShaderUniforms.h" +#include "GPU/Common/FragmentShaderGenerator.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUState.h" + +#define WRITE(p, ...) p.F(__VA_ARGS__) + +bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, uint64_t *uniformMask, std::string *errorString) { + *uniformMask = 0; + errorString->clear(); + + bool highpFog = false; + bool highpTexcoord = false; + bool enableFragmentTestCache = g_Config.bFragmentTestCache && ShaderLanguageIsOpenGL(compat.shaderLanguage); + + if (compat.gles) { + // PowerVR needs highp to do the fog in MHU correctly. + // Others don't, and some can't handle highp in the fragment shader. + highpFog = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? true : false; + highpTexcoord = highpFog; + } + + ReplaceAlphaType stencilToAlpha = static_cast(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)); + + std::vector gl_exts; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE && gl_extensions.EXT_blend_func_extended) { + gl_exts.push_back("#extension GL_EXT_blend_func_extended : require"); + } + if (gl_extensions.EXT_gpu_shader4) { + gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable"); + } + if (compat.framebufferFetchExtension) { + gl_exts.push_back(compat.framebufferFetchExtension); + } + } + + ShaderWriter p(buffer, compat, ShaderStage::Fragment, gl_exts.data(), gl_exts.size()); + + bool lmode = id.Bit(FS_BIT_LMODE); + bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); + bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); + bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); + + bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); + bool testForceToZero = id.Bit(FS_BIT_TEST_DISCARD_TO_ZERO); + bool enableColorTest = id.Bit(FS_BIT_COLOR_TEST); + bool colorTestAgainstZero = id.Bit(FS_BIT_COLOR_AGAINST_ZERO); + bool enableColorDoubling = id.Bit(FS_BIT_COLOR_DOUBLE); + 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); + bool bgraTexture = id.Bit(FS_BIT_BGRA_TEXTURE); + bool colorWriteMask = id.Bit(FS_BIT_COLOR_WRITEMASK); + + if (colorWriteMask && !compat.bitwiseOps) { + *errorString = "Color Write Mask requires bitwise ops"; + return false; + } + + GEComparison alphaTestFunc = (GEComparison)id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3); + GEComparison colorTestFunc = (GEComparison)id.Bits(FS_BIT_COLOR_TEST_FUNC, 2); + bool needShaderTexClamp = id.Bit(FS_BIT_SHADER_TEX_CLAMP); + + GETexFunc texFunc = (GETexFunc)id.Bits(FS_BIT_TEXFUNC, 3); + bool textureAtOffset = id.Bit(FS_BIT_TEXTURE_AT_OFFSET); + + ReplaceBlendType replaceBlend = static_cast(id.Bits(FS_BIT_REPLACE_BLEND, 3)); + + GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); + GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); + GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); + StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4); + + bool isModeClear = id.Bit(FS_BIT_CLEARMODE); + + const char *shading = ""; + if (compat.glslES30 || compat.shaderLanguage == ShaderLanguage::GLSL_VULKAN) + shading = doFlatShading ? "flat" : ""; + + bool earlyFragmentTests = ((!enableAlphaTest && !enableColorTest) || testForceToZero) && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); + bool useAdrenoBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL); + + bool readFramebuffer = replaceBlend == REPLACE_BLEND_COPY_FBO || colorWriteMask; + bool readFramebufferTex = readFramebuffer && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); + + bool needFragCoord = readFramebuffer || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); + bool writeDepth = gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); + + if (shaderDepal && !doTexture) { + *errorString = "depal requires a texture"; + return false; + } + + if (readFramebuffer && compat.shaderLanguage == HLSL_D3D9) { + *errorString = "Framebuffer read not yet supported in HLSL D3D9"; + return false; + } + + if (compat.shaderLanguage == ShaderLanguage::GLSL_VULKAN) { + if (earlyFragmentTests) { + WRITE(p, "layout (early_fragment_tests) in;\n"); + } else if (useAdrenoBugWorkaround && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { + WRITE(p, "layout (depth_unchanged) out float gl_FragDepth;\n"); + } + + WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseUBO {\n%s};\n", ub_baseStr); + if (doTexture) { + WRITE(p, "layout (binding = 0) uniform sampler2D tex;\n"); + } + + if (readFramebufferTex) { + WRITE(p, "layout (binding = 1) uniform sampler2D fbotex;\n"); + } + + if (shaderDepal) { + WRITE(p, "layout (binding = 2) uniform sampler2D pal;\n"); + } + + // Note: the precision qualifiers must match the vertex shader! + WRITE(p, "layout (location = 1) %s in lowp vec4 v_color0;\n", shading); + if (lmode) + WRITE(p, "layout (location = 2) %s in lowp vec3 v_color1;\n", shading); + if (enableFog) { + WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n"); + } + if (doTexture) { + WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n"); + } + + if (enableAlphaTest && !alphaTestAgainstZero) { + WRITE(p, "int roundAndScaleTo255i(in float x) { return int(floor(x * 255.0 + 0.5)); }\n"); + } + if (enableColorTest && !colorTestAgainstZero) { + WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); + } + + WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n"); + if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { + WRITE(p, "layout (location = 0, index = 1) out vec4 fragColor1;\n"); + } + } else if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) { + if (compat.shaderLanguage == HLSL_D3D9) { + if (doTexture) + WRITE(p, "sampler tex : register(s0);\n"); + + if (readFramebufferTex) { + WRITE(p, "vec2 u_fbotexSize : register(c%i);\n", CONST_PS_FBOTEXSIZE); + WRITE(p, "sampler fbotex : register(s1);\n"); + } + + if (replaceBlend > REPLACE_BLEND_STANDARD) { + if (replaceBlendFuncA >= GE_SRCBLEND_FIXA) { + WRITE(p, "float3 u_blendFixA : register(c%i);\n", CONST_PS_BLENDFIXA); + } + if (replaceBlendFuncB >= GE_DSTBLEND_FIXB) { + WRITE(p, "float3 u_blendFixB : register(c%i);\n", CONST_PS_BLENDFIXB); + } + } + if (needShaderTexClamp && doTexture) { + WRITE(p, "vec4 u_texclamp : register(c%i);\n", CONST_PS_TEXCLAMP); + if (textureAtOffset) { + WRITE(p, "vec2 u_texclampoff : register(c%i);\n", CONST_PS_TEXCLAMPOFF); + } + } + + if (enableAlphaTest || enableColorTest) { + WRITE(p, "vec4 u_alphacolorref : register(c%i);\n", CONST_PS_ALPHACOLORREF); + WRITE(p, "vec4 u_alphacolormask : register(c%i);\n", CONST_PS_ALPHACOLORMASK); + } + if (stencilToAlpha && replaceAlphaWithStencilType == STENCIL_VALUE_UNIFORM) { + WRITE(p, "float u_stencilReplaceValue : register(c%i);\n", CONST_PS_STENCILREPLACE); + } + if (doTexture && texFunc == GE_TEXFUNC_BLEND) { + WRITE(p, "float3 u_texenv : register(c%i);\n", CONST_PS_TEXENV); + } + if (enableFog) { + WRITE(p, "float3 u_fogcolor : register(c%i);\n", CONST_PS_FOGCOLOR); + } + } else { + WRITE(p, "SamplerState samp : register(s0);\n"); + WRITE(p, "Texture2D tex : register(t0);\n"); + if (readFramebufferTex) { + // No sampler required, we Load + WRITE(p, "Texture2D fboTex : register(t1);\n"); + } + WRITE(p, "cbuffer base : register(b0) {\n%s};\n", ub_baseStr); + } + + if (enableAlphaTest) { + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "int roundAndScaleTo255i(float x) { return int(floor(x * 255.0f + 0.5f)); }\n"); + } else { + // D3D11 level 9 gets to take this path. + WRITE(p, "float roundAndScaleTo255f(float x) { return floor(x * 255.0f + 0.5f); }\n"); + } + } + if (enableColorTest) { + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "uvec3 roundAndScaleTo255iv(float3 x) { return (floor(x * 255.0f + 0.5f)); }\n"); + } else { + WRITE(p, "vec3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); + } + } + + WRITE(p, "struct PS_IN {\n"); + if (doTexture) { + WRITE(p, " vec3 v_texcoord: TEXCOORD0;\n"); + } + const char *colorInterpolation = doFlatShading && compat.shaderLanguage == HLSL_D3D11 ? "nointerpolation " : ""; + WRITE(p, " %svec4 v_color0: COLOR0;\n", colorInterpolation); + if (lmode) { + WRITE(p, " vec3 v_color1: COLOR1;\n"); + } + if (enableFog) { + WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); + } + if (compat.shaderLanguage == HLSL_D3D11 && needFragCoord) { + WRITE(p, " vec4 pixelPos : SV_POSITION;\n"); + } + WRITE(p, "};\n"); + + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "struct PS_OUT {\n"); + if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { + WRITE(p, " vec4 target : SV_Target0;\n"); + WRITE(p, " vec4 target1 : SV_Target1;\n"); + } else { + WRITE(p, " vec4 target : SV_Target;\n"); + } + if (writeDepth) { + WRITE(p, " float depth : SV_Depth;\n"); + } + WRITE(p, "};\n"); + } + } else if (compat.shaderLanguage == HLSL_D3D9) { + if (doTexture) + WRITE(p, "sampler tex : register(s0);\n"); + if (readFramebufferTex) { + WRITE(p, "vec2 u_fbotexSize : register(c%i);\n", CONST_PS_FBOTEXSIZE); + WRITE(p, "sampler fbotex : register(s1);\n"); + } + if (replaceBlend > REPLACE_BLEND_STANDARD) { + if (replaceBlendFuncA >= GE_SRCBLEND_FIXA) { + WRITE(p, "float3 u_blendFixA : register(c%i);\n", CONST_PS_BLENDFIXA); + } + if (replaceBlendFuncB >= GE_DSTBLEND_FIXB) { + WRITE(p, "float3 u_blendFixB : register(c%i);\n", CONST_PS_BLENDFIXB); + } + } + if (needShaderTexClamp && doTexture) { + WRITE(p, "vec4 u_texclamp : register(c%i);\n", CONST_PS_TEXCLAMP); + if (textureAtOffset) { + WRITE(p, "vec2 u_texclampoff : register(c%i);\n", CONST_PS_TEXCLAMPOFF); + } + } + + if (enableAlphaTest || enableColorTest) { + WRITE(p, "vec4 u_alphacolorref : register(c%i);\n", CONST_PS_ALPHACOLORREF); + WRITE(p, "vec4 u_alphacolormask : register(c%i);\n", CONST_PS_ALPHACOLORMASK); + } + if (stencilToAlpha && replaceAlphaWithStencilType == STENCIL_VALUE_UNIFORM) { + WRITE(p, "float u_stencilReplaceValue : register(c%i);\n", CONST_PS_STENCILREPLACE); + } + if (doTexture && texFunc == GE_TEXFUNC_BLEND) { + WRITE(p, "float3 u_texenv : register(c%i);\n", CONST_PS_TEXENV); + } + if (enableFog) { + WRITE(p, "float3 u_fogcolor : register(c%i);\n", CONST_PS_FOGCOLOR); + } + } else if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (shaderDepal && gl_extensions.IsGLES) { + WRITE(p, "precision highp int;\n"); + } + + if (doTexture) + WRITE(p, "uniform sampler2D tex;\n"); + + if (readFramebufferTex) { + if (!compat.texelFetch) { + WRITE(p, "uniform vec2 u_fbotexSize;\n"); + } + WRITE(p, "uniform sampler2D fbotex;\n"); + } + + if (!isModeClear && replaceBlend > REPLACE_BLEND_STANDARD) { + *uniformMask |= DIRTY_SHADERBLEND; + if (replaceBlendFuncA >= GE_SRCBLEND_FIXA) { + WRITE(p, "uniform vec3 u_blendFixA;\n"); + } + if (replaceBlendFuncB >= GE_DSTBLEND_FIXB) { + WRITE(p, "uniform vec3 u_blendFixB;\n"); + } + } + + if (needShaderTexClamp && doTexture) { + *uniformMask |= DIRTY_TEXCLAMP; + WRITE(p, "uniform vec4 u_texclamp;\n"); + if (id.Bit(FS_BIT_TEXTURE_AT_OFFSET)) { + WRITE(p, "uniform vec2 u_texclampoff;\n"); + } + } + + if (enableAlphaTest || enableColorTest) { + if (enableFragmentTestCache) { + WRITE(p, "uniform sampler2D testtex;\n"); + } else { + *uniformMask |= DIRTY_ALPHACOLORREF; + WRITE(p, "uniform vec4 u_alphacolorref;\n"); + if (compat.bitwiseOps && ((enableColorTest && !colorTestAgainstZero) || (enableAlphaTest && !alphaTestAgainstZero))) { + *uniformMask |= DIRTY_ALPHACOLORMASK; + WRITE(p, "uniform ivec4 u_alphacolormask;\n"); + } + } + } + + if (shaderDepal) { + WRITE(p, "uniform sampler2D pal;\n"); + WRITE(p, "uniform uint u_depal_mask_shift_off_fmt;\n"); + *uniformMask |= DIRTY_DEPAL; + } + + if (colorWriteMask) { + WRITE(p, "uniform uint u_colorWriteMask;\n"); + *uniformMask |= DIRTY_COLORWRITEMASK; + } + + if (stencilToAlpha && replaceAlphaWithStencilType == STENCIL_VALUE_UNIFORM) { + *uniformMask |= DIRTY_STENCILREPLACEVALUE; + WRITE(p, "uniform float u_stencilReplaceValue;\n"); + } + if (doTexture && texFunc == GE_TEXFUNC_BLEND) { + *uniformMask |= DIRTY_TEXENV; + WRITE(p, "uniform vec3 u_texenv;\n"); + } + + WRITE(p, "%s %s vec4 v_color0;\n", shading, compat.varying_fs); + if (lmode) + WRITE(p, "%s %s vec3 v_color1;\n", shading, compat.varying_fs); + if (enableFog) { + *uniformMask |= DIRTY_FOGCOLOR; + WRITE(p, "uniform vec3 u_fogcolor;\n"); + WRITE(p, "%s %s float v_fogdepth;\n", compat.varying_fs, highpFog ? "highp" : "mediump"); + } + if (doTexture) { + WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump"); + } + + if (!enableFragmentTestCache) { + if (enableAlphaTest && !alphaTestAgainstZero) { + if (compat.bitwiseOps) { + WRITE(p, "int roundAndScaleTo255i(in float x) { return int(floor(x * 255.0 + 0.5)); }\n"); + } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { + WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); + } else { + WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); + } + } + if (enableColorTest && !colorTestAgainstZero) { + if (compat.bitwiseOps) { + WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); + } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { + WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); + } else { + WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); + } + } + } + + if (!strcmp(compat.fragColor0, "fragColor0")) { + const char *qualifierColor0 = "out"; + if (compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) { + qualifierColor0 = "inout"; + } + // Output the output color definitions. + if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { + WRITE(p, "%s vec4 fragColor0;\n", qualifierColor0); + WRITE(p, "out vec4 fragColor1;\n"); + } else { + WRITE(p, "%s vec4 fragColor0;\n", qualifierColor0); + } + } + } + + bool hasPackUnorm4x8 = false; + if (compat.shaderLanguage == GLSL_VULKAN) { + hasPackUnorm4x8 = true; + } else if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (compat.gles) { + hasPackUnorm4x8 = compat.glslVersionNumber >= 310; + } else { + hasPackUnorm4x8 = compat.glslVersionNumber >= 400; + } + } + + // Provide implementations of packUnorm4x8 and unpackUnorm4x8 if not available. + if (colorWriteMask && !hasPackUnorm4x8) { + WRITE(p, "uint packUnorm4x8(vec4 v) {\n"); + WRITE(p, " v = clamp(v, 0.0, 1.0);\n"); + WRITE(p, " uvec4 u = uvec4(255.0 * v);\n"); + WRITE(p, " return u.x | (u.y << 8) | (u.z << 16) | (u.w << 24);\n"); + WRITE(p, "}\n"); + + WRITE(p, "vec4 unpackUnorm4x8(uint x) {\n"); + WRITE(p, " uvec4 u = uvec4(x & 0xFFU, (x >> 8) & 0xFFU, (x >> 16) & 0xFFU, (x >> 24) & 0xFFU);\n"); + WRITE(p, " return vec4(u) * (1.0 / 255.0);\n"); + WRITE(p, "}\n"); + } + + // PowerVR needs a custom modulo function. For some reason, this has far higher precision than the builtin one. + if ((gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) && needShaderTexClamp) { + WRITE(p, "float mymod(float a, float b) { return a - b * floor(a / b); }\n"); + } + + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "PS_OUT main( PS_IN In ) {\n"); + WRITE(p, " PS_OUT outfragment;\n"); + if (needFragCoord) { + WRITE(p, " vec4 gl_FragCoord = In.pixelPos;\n"); + } + if (writeDepth) { + WRITE(p, " float gl_FragDepth;\n"); + } + } else if (compat.shaderLanguage == HLSL_D3D9) { + WRITE(p, "vec4 main( PS_IN In ) : COLOR {\n"); + WRITE(p, " vec4 target;\n"); + } else { + WRITE(p, "void main() {\n"); + } + + if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) { + WRITE(p, " vec4 v_color0 = In.v_color0;\n"); + if (lmode) + WRITE(p, " vec3 v_color1 = In.v_color1;\n"); + if (enableFog) { + WRITE(p, " float v_fogdepth = In.v_fogdepth;\n"); + } + if (doTexture) { + WRITE(p, " vec3 v_texcoord = In.v_texcoord;\n"); + } + } + + // Two things read from the old framebuffer - shader replacement blending and bit-level masking. + if (readFramebuffer) { + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, " vec4 destColor = fboTex.Load(int3((int)gl_FragCoord.x, (int)gl_FragCoord.y, 0));\n"); + } else if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { + // If we have EXT_shader_framebuffer_fetch / ARM_shader_framebuffer_fetch, we skip the blit. + // We can just read the prev value more directly. + WRITE(p, " lowp vec4 destColor = %s;\n", compat.lastFragData); + } else if (!compat.texelFetch) { + WRITE(p, " lowp vec4 destColor = %s(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", compat.texture); + } else { + WRITE(p, " lowp vec4 destColor = %s(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n", compat.texelFetch); + } + } + + if (isModeClear) { + // Clear mode does not allow any fancy shading. + WRITE(p, " vec4 v = v_color0;\n"); + } else { + const char *secondary = ""; + // Secondary color for specular on top of texture + if (lmode) { + WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); + secondary = " + s"; + } else { + secondary = ""; + } + + if (doTexture) { + char texcoord[64] = "v_texcoord"; + // TODO: Not sure the right way to do this for projection. + // This path destroys resolution on older PowerVR no matter what I do if projection is needed, + // so we disable it on SGX 540 and lesser, and live with the consequences. + bool terriblePrecision = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_TERRIBLE) != 0; + bool clampDisabled = doTextureProjection && terriblePrecision; + // Also with terrible precision we can't do wrapping without destroying the image. See #9189 + if (terriblePrecision && (!id.Bit(FS_BIT_CLAMP_S) || !id.Bit(FS_BIT_CLAMP_T))) { + clampDisabled = true; + } + if (needShaderTexClamp && !clampDisabled) { + // We may be clamping inside a larger surface (tex = 64x64, buffer=480x272). + // We may also be wrapping in such a surface, or either one in a too-small surface. + // Obviously, clamping to a smaller surface won't work. But better to clamp to something. + std::string ucoord = "v_texcoord.x"; + std::string vcoord = "v_texcoord.y"; + if (doTextureProjection) { + ucoord = "(v_texcoord.x / v_texcoord.z)"; + vcoord = "(v_texcoord.y / v_texcoord.z)"; + } + + std::string modulo = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? "mymod" : "mod"; + + if (id.Bit(FS_BIT_CLAMP_S)) { + ucoord = "clamp(" + ucoord + ", u_texclamp.z, u_texclamp.x - u_texclamp.z)"; + } else { + ucoord = modulo + "(" + ucoord + ", u_texclamp.x)"; + } + if (id.Bit(FS_BIT_CLAMP_T)) { + vcoord = "clamp(" + vcoord + ", u_texclamp.w, u_texclamp.y - u_texclamp.w)"; + } else { + vcoord = modulo + "(" + vcoord + ", u_texclamp.y)"; + } + if (textureAtOffset) { + ucoord = "(" + ucoord + " + u_texclampoff.x)"; + vcoord = "(" + vcoord + " + u_texclampoff.y)"; + } + + WRITE(p, " vec2 fixedcoord = vec2(%s, %s);\n", ucoord.c_str(), vcoord.c_str()); + truncate_cpy(texcoord, "fixedcoord"); + // We already projected it. + doTextureProjection = false; + } + + if (!shaderDepal) { + if (compat.shaderLanguage == HLSL_D3D11) { + if (doTextureProjection) { + WRITE(p, " vec4 t = tex.Sample(samp, v_texcoord.xy / v_texcoord.z)%s;\n", bgraTexture ? ".bgra" : ""); + } else { + WRITE(p, " vec4 t = tex.Sample(samp, %s.xy)%s;\n", texcoord, bgraTexture ? ".bgra" : ""); + } + } else if (compat.shaderLanguage == HLSL_D3D9) { + if (doTextureProjection) { + WRITE(p, " vec4 t = tex2Dproj(tex, vec4(v_texcoord.x, v_texcoord.y, 0, v_texcoord.z))%s;\n", bgraTexture ? ".bgra" : ""); + } else { + WRITE(p, " vec4 t = tex2D(tex, %s.xy)%s;\n", texcoord, bgraTexture ? ".bgra" : ""); + } + } else { + if (doTextureProjection) { + WRITE(p, " vec4 t = %sProj(tex, %s);\n", compat.texture, texcoord); + } else { + WRITE(p, " vec4 t = %s(tex, %s.xy);\n", compat.texture, texcoord); + } + } + } else { + if (doTextureProjection) { + // We don't use textureProj because we need better control and it's probably not much of a savings anyway. + // However it is good for precision on older hardware like PowerVR. + WRITE(p, " vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord); + } else { + WRITE(p, " vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord); + } + WRITE(p, " vec2 tsize = vec2(textureSize(tex, 0));\n"); + WRITE(p, " vec2 fraction;\n"); + WRITE(p, " bool bilinear = (u_depal_mask_shift_off_fmt >> 31) != 0U;\n"); + WRITE(p, " if (bilinear) {\n"); + WRITE(p, " uv_round = uv * tsize - vec2(0.5, 0.5);\n"); + WRITE(p, " fraction = fract(uv_round);\n"); + WRITE(p, " uv_round = (uv_round - fraction + vec2(0.5, 0.5)) / tsize;\n"); // We want to take our four point samples at pixel centers. + WRITE(p, " } else {\n"); + WRITE(p, " uv_round = uv;\n"); + WRITE(p, " }\n"); + WRITE(p, " highp vec4 t = %s(tex, uv_round);\n", compat.texture); + WRITE(p, " highp vec4 t1 = %sOffset(tex, uv_round, ivec2(1, 0));\n", compat.texture); + WRITE(p, " highp vec4 t2 = %sOffset(tex, uv_round, ivec2(0, 1));\n", compat.texture); + WRITE(p, " highp vec4 t3 = %sOffset(tex, uv_round, ivec2(1, 1));\n", compat.texture); + WRITE(p, " uint depalMask = (u_depal_mask_shift_off_fmt & 0xFFU);\n"); + WRITE(p, " uint depalShift = (u_depal_mask_shift_off_fmt >> 8) & 0xFFU;\n"); + WRITE(p, " uint depalOffset = ((u_depal_mask_shift_off_fmt >> 16) & 0xFFU) << 4;\n"); + WRITE(p, " uint depalFmt = (u_depal_mask_shift_off_fmt >> 24) & 0x3U;\n"); + 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 0U:\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 1U:\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 2U:\n"); // 4444 + WRITE(p, " col = uvec4(t.rgba * 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 * 15.99);\n"); + WRITE(p, " index1 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); + WRITE(p, " col = uvec4(t2.rgba * 15.99);\n"); + WRITE(p, " index2 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); + WRITE(p, " col = uvec4(t3.rgba * 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 3U:\n"); // 8888 + WRITE(p, " col = uvec4(t.rgba * 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 * 255.99);\n"); + WRITE(p, " index1 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); + WRITE(p, " col = uvec4(t2.rgba * 255.99);\n"); + WRITE(p, " index2 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); + WRITE(p, " col = uvec4(t3.rgba * 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 && !(index0 == index1 && index1 == index2 && index2 == index3)) {\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) { + case GE_TEXFUNC_MODULATE: + WRITE(p, " vec4 v = p * t%s;\n", secondary); + break; + + case GE_TEXFUNC_DECAL: + WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_BLEND: + WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_REPLACE: + WRITE(p, " vec4 v = t%s;\n", secondary); + break; + + case GE_TEXFUNC_ADD: + case GE_TEXFUNC_UNKNOWN1: + case GE_TEXFUNC_UNKNOWN2: + case GE_TEXFUNC_UNKNOWN3: + WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); + break; + default: + WRITE(p, " vec4 v = p;\n"); break; + } + } else { // texfmt == RGB + switch (texFunc) { + case GE_TEXFUNC_MODULATE: + WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_DECAL: + WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_BLEND: + WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_REPLACE: + WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); + break; + + case GE_TEXFUNC_ADD: + case GE_TEXFUNC_UNKNOWN1: + case GE_TEXFUNC_UNKNOWN2: + case GE_TEXFUNC_UNKNOWN3: + WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; + default: + WRITE(p, " vec4 v = p;\n"); break; + } + } + + if (enableColorDoubling) { + // This happens before fog is applied. + WRITE(p, " v.rgb = clamp(v.rgb * 2.0, 0.0, 1.0);\n"); + } + } else { + // No texture mapping + WRITE(p, " vec4 v = v_color0 %s;\n", secondary); + } + + if (enableFog) { + WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); + WRITE(p, " v = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); + } + + // Texture access is at half texels [0.5/256, 255.5/256], but colors are normalized [0, 255]. + // So we have to scale to account for the difference. + char alphaTestXCoord[64] = "0"; + if (enableFragmentTestCache) { + if (enableColorTest && !colorTestAgainstZero) { + WRITE(p, " vec4 vScale256 = v * %f + %f;\n", 255.0 / 256.0, 0.5 / 256.0); + truncate_cpy(alphaTestXCoord, "vScale256.a"); + } else if (enableAlphaTest && !alphaTestAgainstZero) { + snprintf(alphaTestXCoord, sizeof(alphaTestXCoord), "v.a * %f + %f", 255.0 / 256.0, 0.5 / 256.0); + } + } + + const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "DISCARD;"; + if (enableAlphaTest) { + if (alphaTestAgainstZero) { + // When testing against 0 (extremely common), we can avoid some math. + // 0.002 is approximately half of 1.0 / 255.0. + if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { + WRITE(p, " if (v.a < 0.002) %s\n", discardStatement); + } else if (alphaTestFunc != GE_COMP_NEVER) { + // Anything else is a test for == 0. Happens sometimes, actually... + WRITE(p, " if (v.a > 0.002) %s\n", discardStatement); + } else { + // NEVER has been logged as used by games, although it makes little sense - statically failing. + // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. + WRITE(p, " %s\n", discardStatement); + } + } else if (enableFragmentTestCache) { + WRITE(p, " float aResult = %s(testtex, vec2(%s, 0)).a;\n", compat.texture, alphaTestXCoord); + WRITE(p, " if (aResult < 0.5) %s\n", discardStatement); + } else { + const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; + if (alphaTestFuncs[alphaTestFunc][0] != '#') { + if (compat.bitwiseOps) { + WRITE(p, " if ((roundAndScaleTo255i(v.a) & u_alphacolormask.a) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); + } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { + // Work around bad PVR driver problem where equality check + discard just doesn't work. + if (alphaTestFunc != GE_COMP_NOTEQUAL) { + WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); + } + } else { + WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); + } + } else { + // This means NEVER. See above. + WRITE(p, " %s\n", discardStatement); + } + } + } + + if (enableColorTest) { + if (colorTestAgainstZero) { + // When testing against 0 (common), we can avoid some math. + // 0.002 is approximately half of 1.0 / 255.0. + if (colorTestFunc == GE_COMP_NOTEQUAL) { + WRITE(p, " if (v.r < 0.002 && v.g < 0.002 && v.b < 0.002) %s\n", discardStatement); + } else if (colorTestFunc != GE_COMP_NEVER) { + // Anything else is a test for == 0. + WRITE(p, " if (v.r > 0.002 || v.g > 0.002 || v.b > 0.002) %s\n", discardStatement); + } else { + // NEVER has been logged as used by games, although it makes little sense - statically failing. + // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. + WRITE(p, " %s\n", discardStatement); + } + } else if (enableFragmentTestCache) { + WRITE(p, " float rResult = %s(testtex, vec2(vScale256.r, 0)).r;\n", compat.texture); + WRITE(p, " float gResult = %s(testtex, vec2(vScale256.g, 0)).g;\n", compat.texture); + WRITE(p, " float bResult = %s(testtex, vec2(vScale256.b, 0)).b;\n", compat.texture); + if (colorTestFunc == GE_COMP_EQUAL) { + // Equal means all parts must be equal (so discard if any is not.) + WRITE(p, " if (rResult < 0.5 || gResult < 0.5 || bResult < 0.5) %s\n", discardStatement); + } else { + // Not equal means any part must be not equal. + WRITE(p, " if (rResult < 0.5 && gResult < 0.5 && bResult < 0.5) %s\n", discardStatement); + } + } else { + const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; + if (colorTestFuncs[colorTestFunc][0] != '#') { + // TODO: Unify these paths better. + if (compat.shaderLanguage == HLSL_D3D11) { + const char *test = colorTestFuncs[colorTestFunc]; + WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); + WRITE(p, " uvec3 v_masked = v_scaled & u_alphacolormask.rgb;\n"); + WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & u_alphacolormask.rgb;\n"); + // We have to test the components separately, or we get incorrect results. See #10629. + WRITE(p, " if (v_masked.r %s colorTestRef.r && v_masked.g %s colorTestRef.g && v_masked.b %s colorTestRef.b) %s\n", test, test, test, discardStatement); + } else if (compat.shaderLanguage == HLSL_D3D9) { + const char *test = colorTestFuncs[colorTestFunc]; + // TODO: Use a texture to lookup bitwise ops instead? + WRITE(p, " vec3 colortest = roundAndScaleTo255v(v.rgb);\n"); + WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) %s\n", test, test, test, discardStatement); + } else if (compat.bitwiseOps) { + // Apparently GLES3 does not support vector bitwise ops. + WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); + if (compat.shaderLanguage == GLSL_VULKAN) { + // TODO: Use this for GL as well? + WRITE(p, " if ((v_scaled & u_alphacolormask.rgb) %s (u_alphacolorref.rgb & u_alphacolormask.rgb)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + } else { + const char *maskedFragColor = "ivec3(v_scaled.r & u_alphacolormask.r, v_scaled.g & u_alphacolormask.g, v_scaled.b & u_alphacolormask.b)"; + const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & u_alphacolormask.r, int(u_alphacolorref.g) & u_alphacolormask.g, int(u_alphacolorref.b) & u_alphacolormask.b)"; + WRITE(p, " if (%s %s %s) %s\n", maskedFragColor, colorTestFuncs[colorTestFunc], maskedColorRef, discardStatement); + } + } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { + WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + } else { + WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); + } + } else { + WRITE(p, " %s\n", discardStatement); + } + } + } + + if (replaceBlend == REPLACE_BLEND_2X_SRC) { + WRITE(p, " v.rgb = v.rgb * 2.0;\n"); + } + + if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { + const char *srcFactor = "ERROR"; + switch (replaceBlendFuncA) { + case GE_SRCBLEND_DSTCOLOR: srcFactor = "ERROR"; break; + case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "ERROR"; break; + case GE_SRCBLEND_SRCALPHA: srcFactor = "splat3(v.a)"; break; + case GE_SRCBLEND_INVSRCALPHA: srcFactor = "splat3(1.0 - v.a)"; break; + case GE_SRCBLEND_DSTALPHA: srcFactor = "ERROR"; break; + case GE_SRCBLEND_INVDSTALPHA: srcFactor = "ERROR"; break; + case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "splat3(v.a * 2.0)"; break; + case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "splat3(1.0 - v.a * 2.0)"; break; + // PRE_SRC for REPLACE_BLEND_PRE_SRC_2X_ALPHA means "double the src." + // It's close to the same, but clamping can still be an issue. + case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "splat3(2.0)"; break; + case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "ERROR"; break; + case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; + default: srcFactor = "u_blendFixA"; break; + } + + if (!strcmp(srcFactor, "ERROR")) { + *errorString = "Bad replaceblend src factor"; + return false; + } + + WRITE(p, " v.rgb = v.rgb * %s;\n", srcFactor); + } + + if (replaceBlend == REPLACE_BLEND_COPY_FBO) { + const char *srcFactor = nullptr; + const char *dstFactor = nullptr; + + switch (replaceBlendFuncA) { + case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; + case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(splat3(1.0) - destColor.rgb)"; break; + case GE_SRCBLEND_SRCALPHA: srcFactor = "v.aaa"; break; + case GE_SRCBLEND_INVSRCALPHA: srcFactor = "splat3(1.0 - v.a)"; break; + case GE_SRCBLEND_DSTALPHA: srcFactor = "destColor.aaa"; break; + case GE_SRCBLEND_INVDSTALPHA: srcFactor = "(splat3(1.0) - destColor.aaa)"; break; + case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "v.aaa * 2.0"; break; + case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "(splat3(1.0) - v.aaa * 2.0)"; break; + case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "destColor.aaa * 2.0"; break; + case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "(splat3(1.0) - destColor.aaa * 2.0)"; break; + case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; + default: srcFactor = "u_blendFixA"; break; + } + switch (replaceBlendFuncB) { + case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; + case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(splat3(1.0) - v.rgb)"; break; + case GE_DSTBLEND_SRCALPHA: dstFactor = "v.aaa"; break; + case GE_DSTBLEND_INVSRCALPHA: dstFactor = "(splat3(1.0) - v.aaa)"; break; + case GE_DSTBLEND_DSTALPHA: dstFactor = "destColor.aaa"; break; + case GE_DSTBLEND_INVDSTALPHA: dstFactor = "(splat3(1.0) - destColor.aaa)"; break; + case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "v.aaa * 2.0"; break; + case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "(splat3(1.0) - v.aaa * 2.0)"; break; + case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "destColor.aaa * 2.0"; break; + case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "(splat3(1.0) - destColor.aaa * 2.0)"; break; + case GE_DSTBLEND_FIXB: dstFactor = "u_blendFixB"; break; + default: dstFactor = "u_blendFixB"; break; + } + + switch (replaceBlendEq) { + case GE_BLENDMODE_MUL_AND_ADD: + WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); + break; + case GE_BLENDMODE_MUL_AND_SUBTRACT: + WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); + break; + case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: + WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", dstFactor, srcFactor); + break; + case GE_BLENDMODE_MIN: + WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); + break; + case GE_BLENDMODE_MAX: + WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); + break; + case GE_BLENDMODE_ABSDIFF: + WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); + break; + default: + *errorString = "Bad replace blend eq"; + return false; + } + } + + if (replaceBlend == REPLACE_BLEND_2X_ALPHA || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { + WRITE(p, " v.a *= 2.0;\n"); + } + } + + char replacedAlpha[64] = "0.0"; + if (stencilToAlpha != REPLACE_ALPHA_NO) { + switch (replaceAlphaWithStencilType) { + case STENCIL_VALUE_UNIFORM: + truncate_cpy(replacedAlpha, "u_stencilReplaceValue"); + break; + + case STENCIL_VALUE_ZERO: + truncate_cpy(replacedAlpha, "0.0"); + break; + + case STENCIL_VALUE_ONE: + case STENCIL_VALUE_INVERT: + // In invert, we subtract by one, but we want to output one here. + truncate_cpy(replacedAlpha, "1.0"); + break; + + case STENCIL_VALUE_INCR_4: + case STENCIL_VALUE_DECR_4: + // We're adding/subtracting, just by the smallest value in 4-bit. + snprintf(replacedAlpha, sizeof(replacedAlpha), "%f", 1.0 / 15.0); + break; + + case STENCIL_VALUE_INCR_8: + case STENCIL_VALUE_DECR_8: + // We're adding/subtracting, just by the smallest value in 8-bit. + snprintf(replacedAlpha, sizeof(replacedAlpha), "%f", 1.0 / 255.0); + break; + + case STENCIL_VALUE_KEEP: + // Do nothing. We'll mask out the alpha using color mask. + break; + } + } + + switch (stencilToAlpha) { + case REPLACE_ALPHA_DUALSOURCE: + WRITE(p, " %s = vec4(v.rgb, %s);\n", compat.fragColor0, replacedAlpha); + WRITE(p, " %s = vec4(0.0, 0.0, 0.0, v.a);\n", compat.fragColor1); + break; + + case REPLACE_ALPHA_YES: + WRITE(p, " %s = vec4(v.rgb, %s);\n", compat.fragColor0, replacedAlpha); + break; + + case REPLACE_ALPHA_NO: + WRITE(p, " %s = v;\n", compat.fragColor0); + break; + + default: + *errorString = "Bad stencil-to-alpha type, corrupt ID?"; + return false; + } + + // TODO: This could support more ops using the shader blending mechanism. + LogicOpReplaceType replaceLogicOpType = (LogicOpReplaceType)id.Bits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2); + switch (replaceLogicOpType) { + case LOGICOPTYPE_ONE: + WRITE(p, " %s.rgb = splat3(1.0);\n", compat.fragColor0); + break; + case LOGICOPTYPE_INVERT: + WRITE(p, " %s.rgb = splat3(1.0) - %s.rgb;\n", compat.fragColor0, compat.fragColor0); + break; + case LOGICOPTYPE_NORMAL: + break; + + default: + *errorString = "Bad logic op type, corrupt ID?"; + return false; + } + + // Final color computed - apply color write mask. + // TODO: Maybe optimize to only do math on the affected channels? + // Or .. meh. + if (colorWriteMask) { + WRITE(p, " highp uint v32 = packUnorm4x8(%s);\n", compat.fragColor0); + WRITE(p, " highp uint d32 = packUnorm4x8(destColor);\n"); + // Note that the mask has been flipped to the PC way - 1 means write. + WRITE(p, " v32 = (v32 & u_colorWriteMask) | (d32 & ~u_colorWriteMask);\n"); + WRITE(p, " %s = unpackUnorm4x8(v32);\n", compat.fragColor0); + } + + if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { + const double scale = DepthSliceFactor() * 65535.0; + + WRITE(p, " highp float z = gl_FragCoord.z;\n"); + if (gstate_c.Supports(GPU_SUPPORTS_ACCURATE_DEPTH)) { + // We center the depth with an offset, but only its fraction matters. + // When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0. + if (((int)(DepthSliceFactor() - 1.0f) & 1) == 1) { + WRITE(p, " z = (floor((z * %f) - (1.0 / 2.0)) + (1.0 / 2.0)) * (1.0 / %f);\n", scale, scale); + } else { + WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale); + } + } else { + WRITE(p, " z = (1.0/65535.0) * floor(z * 65535.0);\n"); + } + WRITE(p, " gl_FragDepth = z;\n"); + } else if (!earlyFragmentTests && useAdrenoBugWorkaround) { + // Adreno (and possibly MESA/others) apply early frag tests even with discard in the shader. + // Writing depth prevents the bug, even with depth_unchanged specified. + WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n"); + } + + if (compat.shaderLanguage == HLSL_D3D11) { + if (writeDepth) { + WRITE(p, " outfragment.depth = gl_FragDepth;\n"); + } + WRITE(p, " return outfragment;\n"); + } else if (compat.shaderLanguage == HLSL_D3D9) { + WRITE(p, " return target;\n"); + } + + WRITE(p, "}\n"); + + return true; +} + diff --git a/GPU/Directx9/PixelShaderGeneratorDX9.h b/GPU/Common/FragmentShaderGenerator.h similarity index 84% rename from GPU/Directx9/PixelShaderGeneratorDX9.h rename to GPU/Common/FragmentShaderGenerator.h index 15623439ff..2e27d165d0 100644 --- a/GPU/Directx9/PixelShaderGeneratorDX9.h +++ b/GPU/Common/FragmentShaderGenerator.h @@ -17,12 +17,11 @@ #pragma once -#include "GPU/Common/ShaderId.h" -#include "GPU/Common/ShaderCommon.h" +#include "Common/GPU/Shader.h" -namespace DX9 { +struct FShaderID; -bool GenerateFragmentShaderHLSL(const FShaderID &id, char *buffer, ShaderLanguage lang = HLSL_DX9); +// D3D9 constants #define CONST_PS_TEXENV 0 #define CONST_PS_ALPHACOLORREF 1 @@ -38,4 +37,4 @@ bool GenerateFragmentShaderHLSL(const FShaderID &id, char *buffer, ShaderLanguag // For stencil upload #define CONST_PS_STENCILVALUE 10 -}; +bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, uint64_t *uniformMask, std::string *errorString); diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 4353f77c0d..34d2d622e0 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -35,7 +35,9 @@ #include "GPU/Common/PostShader.h" #include "GPU/Common/PresentationCommon.h" #include "GPU/Common/TextureCacheCommon.h" +#include "GPU/Common/ReinterpretFramebuffer.h" #include "GPU/Debugger/Record.h" +#include "GPU/Debugger/Stepping.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" @@ -77,6 +79,7 @@ bool FramebufferManagerCommon::UpdateSize() { renderWidth_ = (float)PSP_CoreParameter().renderWidth; renderHeight_ = (float)PSP_CoreParameter().renderHeight; + renderScaleFactor_ = (float)PSP_CoreParameter().renderScaleFactor; pixelWidth_ = PSP_CoreParameter().pixelWidth; pixelHeight_ = PSP_CoreParameter().pixelHeight; bloomHack_ = g_Config.iBloomHack; @@ -267,10 +270,13 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame if (v->fb_address == params.fb_address) { vfb = v; // Update fb stride in case it changed - if (vfb->fb_stride != params.fb_stride || vfb->format != params.fmt) { - vfbFormatChanged = true; + if (vfb->fb_stride != params.fb_stride) { vfb->fb_stride = params.fb_stride; + vfbFormatChanged = true; + } + if (vfb->format != params.fmt) { vfb->format = params.fmt; + vfbFormatChanged = true; } if (vfb->z_address == 0 && vfb->z_stride == 0 && params.z_stride != 0) { @@ -339,14 +345,6 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame } } - float renderWidthFactor = renderWidth_ / 480.0f; - float renderHeightFactor = renderHeight_ / 272.0f; - - if (PSP_CoreParameter().compat.flags().Force04154000Download && params.fb_address == 0x04154000) { - renderWidthFactor = 1.0; - renderHeightFactor = 1.0; - } - // None found? Create one. if (!vfb) { vfb = new VirtualFramebuffer{}; @@ -355,28 +353,28 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame vfb->fb_stride = params.fb_stride; vfb->z_address = params.z_address; vfb->z_stride = params.z_stride; + + // The other width/height parameters are set in ResizeFramebufFBO below. vfb->width = drawing_width; vfb->height = drawing_height; vfb->newWidth = drawing_width; vfb->newHeight = drawing_height; vfb->lastFrameNewSize = gpuStats.numFlips; - vfb->renderWidth = (u16)(drawing_width * renderWidthFactor); - vfb->renderHeight = (u16)(drawing_height * renderHeightFactor); - vfb->bufferWidth = drawing_width; - vfb->bufferHeight = drawing_height; vfb->format = params.fmt; vfb->drawnFormat = params.fmt; vfb->usageFlags = FB_USAGE_RENDERTARGET; - SetColorUpdated(vfb, skipDrawReason); u32 byteSize = ColorBufferByteSize(vfb); if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + byteSize > framebufRangeEnd_) { framebufRangeEnd_ = params.fb_address + byteSize; } + // This is where we actually create the framebuffer. The true is "force". ResizeFramebufFBO(vfb, drawing_width, drawing_height, true); NotifyRenderFramebufferCreated(vfb); + SetColorUpdated(vfb, skipDrawReason); + INFO_LOG(FRAMEBUF, "Creating FBO for %08x (z: %08x) : %i x %i x %i", vfb->fb_address, vfb->z_address, vfb->width, vfb->height, vfb->format); vfb->last_frame_render = gpuStats.numFlips; @@ -473,7 +471,7 @@ void FramebufferManagerCommon::BlitFramebufferDepth(VirtualFramebuffer *src, Vir int h = std::min(src->renderHeight, dst->renderHeight); // Note: We prefer Blit ahead of Copy here, since at least on GL, Copy will always also copy stencil which we don't want. See #9740. - if (gstate_c.Supports(GPU_SUPPORTS_FRAMEBUFFER_BLIT)) { + if (gstate_c.Supports(GPU_SUPPORTS_FRAMEBUFFER_BLIT_TO_DEPTH)) { draw_->BlitFramebuffer(src->fbo, 0, 0, w, h, dst->fbo, 0, 0, w, h, Draw::FB_DEPTH_BIT, Draw::FB_BLIT_NEAREST, "BlitFramebufferDepth"); RebindFramebuffer("BlitFramebufferDepth"); } else if (gstate_c.Supports(GPU_SUPPORTS_COPY_IMAGE)) { @@ -507,7 +505,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer if (vfbFormatChanged) { textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_UPDATED); if (vfb->drawnFormat != vfb->format) { - ReformatFramebufferFrom(vfb, vfb->drawnFormat); + ReinterpretFramebufferFrom(vfb, vfb->drawnFormat); } } @@ -521,6 +519,120 @@ void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer } } +void FramebufferManagerCommon::ReinterpretFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat oldFormat) { + if (!useBufferedRendering_ || !vfb->fbo) { + return; + } + + ShaderLanguage lang = draw_->GetShaderLanguageDesc().shaderLanguage; + + bool doReinterpret = PSP_CoreParameter().compat.flags().ReinterpretFramebuffers && + (lang == HLSL_D3D11 || lang == GLSL_VULKAN || lang == GLSL_3xx); + if (!doReinterpret) { + // Fake reinterpret - just clear the way we always did on Vulkan. Just clear color and stencil. + if (oldFormat == GE_FORMAT_565) { + // We have to bind here instead of clear, since it can be that no framebuffer is bound. + // The backend can sometimes directly optimize it to a clear. + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "FakeReinterpret"); + // Need to dirty anything that has command buffer dynamic state, in case we started a new pass above. + // Should find a way to feed that information back, maybe... Or simply correct the issue in the rendermanager. + gstate_c.Dirty(DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE); + } + return; + } + + GEBufferFormat newFormat = vfb->format; + + _assert_(newFormat != oldFormat); + + // We only reinterpret between 16 - bit formats, for now. + if (!IsGeBufferFormat16BitColor(oldFormat) || !IsGeBufferFormat16BitColor(newFormat)) { + // 16->32 and 32->16 will require some more specialized shaders. + return; + } + + char *vsCode = nullptr; + char *fsCode = nullptr; + + if (!reinterpretVS_) { + vsCode = new char[4000]; + const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc(); + GenerateReinterpretVertexShader(vsCode, shaderLanguageDesc); + reinterpretVS_ = draw_->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)vsCode, strlen(vsCode), "reinterpret_vs"); + _assert_(reinterpretVS_); + } + + if (!reinterpretSampler_) { + Draw::SamplerStateDesc samplerDesc{}; + samplerDesc.magFilter = Draw::TextureFilter::LINEAR; + samplerDesc.minFilter = Draw::TextureFilter::LINEAR; + reinterpretSampler_ = draw_->CreateSamplerState(samplerDesc); + } + + // See if we need to create a new pipeline. + + Draw::Pipeline *pipeline = reinterpretFromTo_[(int)oldFormat][(int)newFormat]; + if (!pipeline) { + fsCode = new char[4000]; + const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc(); + GenerateReinterpretFragmentShader(fsCode, oldFormat, newFormat, shaderLanguageDesc); + Draw::ShaderModule *reinterpretFS = draw_->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)fsCode, strlen(fsCode), "reinterpret_fs"); + _assert_(reinterpretFS); + + std::vector shaders; + shaders.push_back(reinterpretVS_); + shaders.push_back(reinterpretFS); + + using namespace Draw; + Draw::PipelineDesc desc{}; + // We use a "fullscreen triangle". + // TODO: clear the stencil buffer. Hard to actually initialize it with the new alpha, though possible - let's see if + // we need it. + DepthStencilState *depth = draw_->CreateDepthStencilState({ false, false, Comparison::LESS }); + BlendState *blendstateOff = draw_->CreateBlendState({ false, 0xF }); + RasterState *rasterNoCull = draw_->CreateRasterState({}); + + // No uniforms for these, only a single texture input. + PipelineDesc pipelineDesc{ Primitive::TRIANGLE_LIST, shaders, nullptr, depth, blendstateOff, rasterNoCull, nullptr }; + pipeline = draw_->CreateGraphicsPipeline(pipelineDesc); + _assert_(pipeline != nullptr); + reinterpretFromTo_[(int)oldFormat][(int)newFormat] = pipeline; + + depth->Release(); + blendstateOff->Release(); + rasterNoCull->Release(); + reinterpretFS->Release(); + } + + // Copy to a temp framebuffer. + Draw::Framebuffer *temp = GetTempFBO(TempFBO::REINTERPRET, vfb->renderWidth, vfb->renderHeight); + + draw_->InvalidateCachedState(); + draw_->CopyFramebufferImage(vfb->fbo, 0, 0, 0, 0, temp, 0, 0, 0, 0, vfb->renderWidth, vfb->renderHeight, 1, Draw::FBChannel::FB_COLOR_BIT, "reinterpret_prep"); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "reinterpret"); + draw_->BindPipeline(pipeline); + draw_->BindFramebufferAsTexture(temp, 0, Draw::FBChannel::FB_COLOR_BIT, 0); + draw_->BindSamplerStates(0, 1, &reinterpretSampler_); + draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); + Draw::Viewport vp = Draw::Viewport{ 0.0f, 0.0f, (float)vfb->renderWidth, (float)vfb->renderHeight, 0.0f, 1.0f }; + draw_->SetViewports(1, &vp); + // No vertex buffer - generate vertices in shader. TODO: Switch to a vertex buffer for GLES2/D3D9 compat. + draw_->Draw(3, 0); + draw_->InvalidateCachedState(); + + // Unbind. + draw_->BindTexture(0, nullptr); + RebindFramebuffer("After reinterpret"); + + shaderManager_->DirtyLastShader(); + textureCache_->ForgetLastTexture(); + + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE); + + delete[] vsCode; + delete[] fsCode; +} + void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffer *prevVfb, VirtualFramebuffer *vfb, bool isClearingDepth) { if (ShouldDownloadFramebuffer(vfb) && !vfb->memoryUpdated) { ReadFramebufferToMemory(vfb, 0, 0, vfb->width, vfb->height); @@ -549,8 +661,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe } } if (vfb->drawnFormat != vfb->format) { - // TODO: Might ultimately combine this with the resize step in DoSetRenderFrameBuffer(). - ReformatFramebufferFrom(vfb, vfb->drawnFormat); + ReinterpretFramebufferFrom(vfb, vfb->drawnFormat); } if (useBufferedRendering_) { @@ -708,6 +819,49 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int } } +bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { + if (!framebuffer->fbo || !useBufferedRendering_) { + draw_->BindTexture(0, nullptr); + gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; + return false; + } + + // currentRenderVfb_ will always be set when this is called, except from the GE debugger. + // Let's just not bother with the copy in that case. + bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0; + if (GPUStepping::IsStepping()) { + skipCopy = true; + } + // Currently rendering to this framebuffer. Need to make a copy. + if (!skipCopy && framebuffer == currentRenderVfb_) { + // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. + Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight); + if (renderCopy) { + VirtualFramebuffer copyInfo = *framebuffer; + copyInfo.fbo = renderCopy; + CopyFramebufferForColorTexture(©Info, framebuffer, flags); + RebindFramebuffer("After BindFramebufferAsColorTexture"); + draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0); + } else { + draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); + } + return true; + } else if (framebuffer != currentRenderVfb_ || (flags & BINDFBCOLOR_FORCE_SELF) != 0) { + draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); + return true; + } else { + ERROR_LOG_REPORT_ONCE(vulkanSelfTexture, G3D, "Attempting to texture from target (src=%08x / target=%08x / flags=%d)", framebuffer->fb_address, currentRenderVfb_->fb_address, flags); + // To do this safely in Vulkan, we need to use input attachments. + // Actually if the texture region and render regions don't overlap, this is safe, but we need + // to transition to GENERAL image layout which will take some trickery. + // Badness on D3D11 to bind the currently rendered-to framebuffer as a texture. + draw_->BindTexture(0, nullptr); + gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; + return false; + } + +} + void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags) { int x = 0; int y = 0; @@ -1045,6 +1199,7 @@ void FramebufferManagerCommon::DecimateFBOs() { } } +// Requires width/height to be set already. void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, int h, bool force, bool skipCopy) { _dbg_assert_(w > 0); _dbg_assert_(h > 0); @@ -1066,12 +1221,36 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, vfb->bufferHeight = std::max((int)vfb->bufferHeight, h); } - SetRenderSize(vfb); + bool force1x = false; + switch (bloomHack_) { + case 1: + force1x = vfb->bufferWidth <= 128 || vfb->bufferHeight <= 64; + break; + case 2: + force1x = vfb->bufferWidth <= 256 || vfb->bufferHeight <= 128; + break; + case 3: + force1x = vfb->bufferWidth < 480 || vfb->bufferWidth > 800 || vfb->bufferHeight < 272; // GOW uses 864x272 + break; + } + + if (PSP_CoreParameter().compat.flags().Force04154000Download && vfb->fb_address == 0x04154000) { + force1x = true; + } + + if (force1x && g_Config.iInternalResolution != 1) { + vfb->renderScaleFactor = 1.0f; + vfb->renderWidth = vfb->bufferWidth; + vfb->renderHeight = vfb->bufferHeight; + } else { + vfb->renderScaleFactor = renderScaleFactor_; + vfb->renderWidth = (u16)(vfb->bufferWidth * renderScaleFactor_); + vfb->renderHeight = (u16)(vfb->bufferHeight * renderScaleFactor_); + } // During hardware rendering, we always render at full color depth even if the game wouldn't on real hardware. // It's not worth the trouble trying to support lower bit-depth rendering, just // more cases to test that nobody will ever use. - vfb->colorDepth = Draw::FBO_8888; textureCache_->ForgetLastTexture(); @@ -1089,8 +1268,8 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, shaderManager_->DirtyLastShader(); char tag[256]; - snprintf(tag, sizeof(tag), "%08x_%08x_%dx%d", vfb->fb_address, vfb->z_address, w, h); - vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, 1, true, (Draw::FBColorDepth)vfb->colorDepth, tag }); + snprintf(tag, sizeof(tag), "%08x_%08x_%dx%d_%s", vfb->fb_address, vfb->z_address, w, h, GeBufferFormatToString(vfb->format)); + vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, 1, true, tag }); if (old.fbo) { INFO_LOG(FRAMEBUF, "Resizing FBO for %08x : %dx%dx%s", vfb->fb_address, w, h, GeBufferFormatToString(vfb->format)); if (vfb->fbo) { @@ -1182,9 +1361,13 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, } } - if (!dstBuffer && srcBuffer && PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB) { - dstBuffer = CreateRAMFramebuffer(dst, srcBuffer->width, srcBuffer->height, srcBuffer->fb_stride, srcBuffer->format); - dstY = 0; + if (!dstBuffer && srcBuffer) { + // Note - if we're here, we're in a memcpy, not a block transfer. Not allowing IntraVRAMBlockTransferAllowCreateFB. + // Technically, that makes BlockTransferAllowCreateFB a bit of a misnomer. + if (PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB) { + dstBuffer = CreateRAMFramebuffer(dst, srcBuffer->width, srcBuffer->height, srcBuffer->fb_stride, srcBuffer->format); + dstY = 0; + } } if (dstBuffer) { dstBuffer->last_frame_used = gpuStats.numFlips; @@ -1310,21 +1493,25 @@ void FramebufferManagerCommon::FindTransferFramebuffers(VirtualFramebuffer *&dst } } - if (srcBuffer && !dstBuffer && PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB) { - GEBufferFormat ramFormat; - // Try to guess the appropriate format. We only know the bpp from the block transfer command (16 or 32 bit). - if (bpp == 4) { - // Only one possibility unless it's doing split pixel tricks (which we could detect through stride maybe). - ramFormat = GE_FORMAT_8888; - } else if (srcBuffer->format != GE_FORMAT_8888) { - // We guess that the game will interpret the data the same as it was in the source of the copy. - // Seems like a likely good guess, and works in Test Drive Unlimited. - ramFormat = srcBuffer->format; - } else { - // No info left - just fall back to something. But this is definitely split pixel tricks. - ramFormat = GE_FORMAT_5551; + if (srcBuffer && !dstBuffer) { + if (PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB || + (PSP_CoreParameter().compat.flags().IntraVRAMBlockTransferAllowCreateFB && + Memory::IsVRAMAddress(srcBuffer->fb_address) && Memory::IsVRAMAddress(dstBasePtr))) { + GEBufferFormat ramFormat; + // Try to guess the appropriate format. We only know the bpp from the block transfer command (16 or 32 bit). + if (bpp == 4) { + // Only one possibility unless it's doing split pixel tricks (which we could detect through stride maybe). + ramFormat = GE_FORMAT_8888; + } else if (srcBuffer->format != GE_FORMAT_8888) { + // We guess that the game will interpret the data the same as it was in the source of the copy. + // Seems like a likely good guess, and works in Test Drive Unlimited. + ramFormat = srcBuffer->format; + } else { + // No info left - just fall back to something. But this is definitely split pixel tricks. + ramFormat = GE_FORMAT_5551; + } + dstBuffer = CreateRAMFramebuffer(dstBasePtr, dstWidth, dstHeight, dstStride, ramFormat); } - dstBuffer = CreateRAMFramebuffer(dstBasePtr, dstWidth, dstHeight, dstStride, ramFormat); } if (dstBuffer) @@ -1341,9 +1528,6 @@ void FramebufferManagerCommon::FindTransferFramebuffers(VirtualFramebuffer *&dst } VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAddress, int width, int height, int stride, GEBufferFormat format) { - float renderWidthFactor = renderWidth_ / 480.0f; - float renderHeightFactor = renderHeight_ / 272.0f; - INFO_LOG(G3D, "Creating RAM framebuffer at %08x (%dx%d, stride %d, format %d)", fbAddress, width, height, stride, format); // A target for the destination is missing - so just create one! @@ -1360,8 +1544,9 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd vfb->newWidth = vfb->width; vfb->newHeight = vfb->height; vfb->lastFrameNewSize = gpuStats.numFlips; - vfb->renderWidth = (u16)(vfb->width * renderWidthFactor); - vfb->renderHeight = (u16)(vfb->height * renderHeightFactor); + vfb->renderScaleFactor = renderScaleFactor_; + vfb->renderWidth = (u16)(vfb->width * renderScaleFactor_); + vfb->renderHeight = (u16)(vfb->height * renderScaleFactor_); vfb->bufferWidth = vfb->width; vfb->bufferHeight = vfb->height; vfb->format = format; @@ -1371,7 +1556,7 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd char name[64]; snprintf(name, sizeof(name), "%08x_color_RAM", vfb->fb_address); textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_CREATED); - vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, 1, true, (Draw::FBColorDepth)vfb->colorDepth, name }); + vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, 1, true, name }); vfbs_.push_back(vfb); u32 byteSize = ColorBufferByteSize(vfb); @@ -1414,16 +1599,19 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram nvfb->height = vfb->height; nvfb->renderWidth = vfb->bufferWidth; nvfb->renderHeight = vfb->bufferHeight; + nvfb->renderScaleFactor = 1.0f; // For readbacks we resize to the original size, of course. nvfb->bufferWidth = vfb->bufferWidth; nvfb->bufferHeight = vfb->bufferHeight; nvfb->format = vfb->format; nvfb->drawnWidth = vfb->drawnWidth; nvfb->drawnHeight = vfb->drawnHeight; nvfb->drawnFormat = vfb->format; - nvfb->colorDepth = vfb->colorDepth; - if (!CreateDownloadTempBuffer(nvfb)) { - delete nvfb; + char name[64]; + snprintf(name, sizeof(name), "download_temp"); + nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, false, name }); + if (!nvfb->fbo) { + ERROR_LOG(FRAMEBUF, "Error creating FBO! %d x %d", nvfb->renderWidth, nvfb->renderHeight); return nullptr; } @@ -1439,36 +1627,6 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram return nvfb; } -bool FramebufferManagerCommon::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) { - // When updating VRAM, it need to be exact format. - if (!gstate_c.Supports(GPU_PREFER_CPU_DOWNLOAD)) { - switch (nvfb->format) { - case GE_FORMAT_4444: - nvfb->colorDepth = Draw::FBO_4444; - break; - case GE_FORMAT_5551: - nvfb->colorDepth = Draw::FBO_5551; - break; - case GE_FORMAT_565: - nvfb->colorDepth = Draw::FBO_565; - break; - case GE_FORMAT_8888: - default: - nvfb->colorDepth = Draw::FBO_8888; - break; - } - } - - char name[64]; - snprintf(name, sizeof(name), "download_temp"); - nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, false, (Draw::FBColorDepth)nvfb->colorDepth, name }); - if (!nvfb->fbo) { - ERROR_LOG(FRAMEBUF, "Error creating FBO! %d x %d", nvfb->renderWidth, nvfb->renderHeight); - return false; - } - return true; -} - void FramebufferManagerCommon::ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor) { if (currentRenderVfb_) { if ((currentRenderVfb_->usageFlags & FB_USAGE_DOWNLOAD_CLEAR) != 0) { @@ -1547,36 +1705,6 @@ void FramebufferManagerCommon::ApplyClearToMemory(int x1, int y1, int x2, int y2 } } -void FramebufferManagerCommon::OptimizeDownloadRange(VirtualFramebuffer * vfb, int & x, int & y, int & w, int & h) { - if (gameUsesSequentialCopies_) { - // Ignore the x/y/etc., read the entire thing. - x = 0; - y = 0; - w = vfb->width; - h = vfb->height; - } - if (x == 0 && y == 0 && w == vfb->width && h == vfb->height) { - // Mark it as fully downloaded until next render to it. - vfb->memoryUpdated = true; - vfb->usageFlags |= FB_USAGE_DOWNLOAD; - } else { - // Let's try to set the flag eventually, if the game copies a lot. - // Some games copy subranges very frequently. - const static int FREQUENT_SEQUENTIAL_COPIES = 3; - static int frameLastCopy = 0; - static u32 bufferLastCopy = 0; - static int copiesThisFrame = 0; - if (frameLastCopy != gpuStats.numFlips || bufferLastCopy != vfb->fb_address) { - frameLastCopy = gpuStats.numFlips; - bufferLastCopy = vfb->fb_address; - copiesThisFrame = 0; - } - if (++copiesThisFrame > FREQUENT_SEQUENTIAL_COPIES) { - gameUsesSequentialCopies_ = true; - } - } -} - bool FramebufferManagerCommon::NotifyBlockTransferBefore(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int width, int height, int bpp, u32 skipDrawReason) { if (!useBufferedRendering_) { return false; @@ -1705,35 +1833,6 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS } } -void FramebufferManagerCommon::SetRenderSize(VirtualFramebuffer *vfb) { - float renderWidthFactor = renderWidth_ / 480.0f; - float renderHeightFactor = renderHeight_ / 272.0f; - bool force1x = false; - switch (bloomHack_) { - case 1: - force1x = vfb->bufferWidth <= 128 || vfb->bufferHeight <= 64; - break; - case 2: - force1x = vfb->bufferWidth <= 256 || vfb->bufferHeight <= 128; - break; - case 3: - force1x = vfb->bufferWidth < 480 || vfb->bufferWidth > 800 || vfb->bufferHeight < 272; // GOW uses 864x272 - break; - } - - if (PSP_CoreParameter().compat.flags().Force04154000Download && vfb->fb_address == 0x04154000) { - force1x = true; - } - - if (force1x && g_Config.iInternalResolution != 1) { - vfb->renderWidth = vfb->bufferWidth; - vfb->renderHeight = vfb->bufferHeight; - } else { - vfb->renderWidth = (u16)(vfb->bufferWidth * renderWidthFactor); - vfb->renderHeight = (u16)(vfb->bufferHeight * renderHeightFactor); - } -} - void FramebufferManagerCommon::SetSafeSize(u16 w, u16 h) { VirtualFramebuffer *vfb = currentRenderVfb_; if (vfb) { @@ -1745,10 +1844,11 @@ void FramebufferManagerCommon::SetSafeSize(u16 w, u16 h) { void FramebufferManagerCommon::Resized() { gstate_c.skipDrawReason &= ~SKIPDRAW_NON_DISPLAYED_FB; - int w, h; - presentation_->CalculateRenderResolution(&w, &h, &postShaderIsUpscalingFilter_, &postShaderIsSupersampling_); + int w, h, scaleFactor; + presentation_->CalculateRenderResolution(&w, &h, &scaleFactor, &postShaderIsUpscalingFilter_, &postShaderIsSupersampling_); PSP_CoreParameter().renderWidth = w; PSP_CoreParameter().renderHeight = h; + PSP_CoreParameter().renderScaleFactor = scaleFactor; if (UpdateSize()) { DestroyAllFBOs(); @@ -1786,10 +1886,15 @@ void FramebufferManagerCommon::DestroyAllFBOs() { tempFB.second.fbo->Release(); } tempFBOs_.clear(); + + for (auto iter : fbosToDelete_) { + iter->Release(); + } + fbosToDelete_.clear(); } -Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h, Draw::FBColorDepth color_depth) { - u64 key = ((u64)reason << 48) | ((u64)color_depth << 32) | ((u32)w << 16) | h; +Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h) { + u64 key = ((u64)reason << 48) | ((u32)w << 16) | h; auto it = tempFBOs_.find(key); if (it != tempFBOs_.end()) { it->second.last_frame_used = gpuStats.numFlips; @@ -1799,7 +1904,7 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u bool z_stencil = reason == TempFBO::STENCIL; char name[128]; snprintf(name, sizeof(name), "temp_fbo_%dx%d%s", w, h, z_stencil ? "_depth" : ""); - Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, z_stencil, color_depth, name }); + Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, z_stencil, name }); if (!fbo) { return nullptr; } @@ -1880,6 +1985,7 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB tempVfb.bufferHeight = vfb->height; tempVfb.renderWidth = w; tempVfb.renderHeight = h; + tempVfb.renderScaleFactor = (float)maxRes; BlitFramebuffer(&tempVfb, 0, 0, vfb, 0, 0, vfb->width, vfb->height, 0); bound = tempFBO; @@ -2036,7 +2142,35 @@ void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, } if (vfb && vfb->fbo) { // We'll pseudo-blit framebuffers here to get a resized version of vfb. - OptimizeDownloadRange(vfb, x, y, w, h); + if (gameUsesSequentialCopies_) { + // Ignore the x/y/etc., read the entire thing. + x = 0; + y = 0; + w = vfb->width; + h = vfb->height; + vfb->memoryUpdated = true; + vfb->usageFlags |= FB_USAGE_DOWNLOAD; + } else if (x == 0 && y == 0 && w == vfb->width && h == vfb->height) { + // Mark it as fully downloaded until next render to it. + vfb->memoryUpdated = true; + vfb->usageFlags |= FB_USAGE_DOWNLOAD; + } else { + // Let's try to set the flag eventually, if the game copies a lot. + // Some games copy subranges very frequently. + const static int FREQUENT_SEQUENTIAL_COPIES = 3; + static int frameLastCopy = 0; + static u32 bufferLastCopy = 0; + static int copiesThisFrame = 0; + if (frameLastCopy != gpuStats.numFlips || bufferLastCopy != vfb->fb_address) { + frameLastCopy = gpuStats.numFlips; + bufferLastCopy = vfb->fb_address; + copiesThisFrame = 0; + } + if (++copiesThisFrame > FREQUENT_SEQUENTIAL_COPIES) { + gameUsesSequentialCopies_ = true; + } + } + if (vfb->renderWidth == vfb->width && vfb->renderHeight == vfb->height) { // No need to blit PackFramebufferSync_(vfb, x, y, w, h); @@ -2080,7 +2214,8 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo // No need to download if we already have it. if (w > 0 && h > 0 && !vfb->memoryUpdated && vfb->clutUpdatedBytes < loadBytes) { - // We intentionally don't call OptimizeDownloadRange() here - we don't want to over download. + // We intentionally don't try to optimize into a full download here - we don't want to over download. + // CLUT framebuffers are often incorrectly estimated in size. if (x == 0 && y == 0 && w == vfb->width && h == vfb->height) { vfb->memoryUpdated = true; @@ -2128,3 +2263,30 @@ std::vector FramebufferManagerCommon::GetFramebufferList() { return list; } + +void FramebufferManagerCommon::DeviceLost() { + DestroyAllFBOs(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (reinterpretFromTo_[i][j]) { + reinterpretFromTo_[i][j]->Release(); + reinterpretFromTo_[i][j] = nullptr; + } + } + } + if (reinterpretSampler_) { + reinterpretSampler_->Release(); + reinterpretSampler_ = nullptr; + } + if (reinterpretVS_) { + reinterpretVS_->Release(); + reinterpretVS_ = nullptr; + } + presentation_->DeviceLost(); + draw_ = nullptr; +} + +void FramebufferManagerCommon::DeviceRestore(Draw::DrawContext *draw) { + draw_ = draw; + presentation_->DeviceRestore(draw); +} diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 44cd2f6b5f..04422c7970 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -55,31 +55,32 @@ struct VirtualFramebuffer { int fb_stride; int z_stride; - GEBufferFormat format; // virtual, right now they are all RGBA8888 + GEBufferFormat format; // virtual, in reality they are all RGBA8888 for better quality but we can reinterpret that as necessary // width/height: The detected size of the current framebuffer, in original PSP pixels. u16 width; u16 height; + // bufferWidth/bufferHeight: The pre-scaling size of the buffer itself. May only be bigger than or equal to width/height. + // Actual physical buffer is this size times the render resolution multiplier. + // The buffer may be used to render a width or height from 0 to these values without being recreated. + u16 bufferWidth; + u16 bufferHeight; + // renderWidth/renderHeight: The scaled size we render at. May be scaled to render at higher resolutions. // The physical buffer may be larger than renderWidth/renderHeight. u16 renderWidth; u16 renderHeight; - // bufferWidth/bufferHeight: The pre-scaling size of the buffer itself. May only be bigger than width/height. - // Actual physical buffer is this size times the render resolution multiplier. - // The buffer may be used to render a width or height from 0 to these values without being recreated. - u16 bufferWidth; - u16 bufferHeight; + float renderScaleFactor; u16 usageFlags; u16 newWidth; u16 newHeight; + int lastFrameNewSize; - // TODO: Handle fbo and colorDepth better. - u8 colorDepth; Draw::Framebuffer *fbo; u16 drawnWidth; @@ -157,6 +158,8 @@ enum class TempFBO { BLIT, // For copies of framebuffers (e.g. shader blending.) COPY, + // For another type of framebuffers that can happen together with COPY (see Outrun) + REINTERPRET, // Used to copy stencil data, means we need a stencil backing. STENCIL, }; @@ -188,7 +191,7 @@ class TextureCacheCommon; class FramebufferManagerCommon { public: - explicit FramebufferManagerCommon(Draw::DrawContext *draw); + FramebufferManagerCommon(Draw::DrawContext *draw); virtual ~FramebufferManagerCommon(); virtual void Init(); @@ -232,6 +235,7 @@ public: bool NotifyBlockTransferBefore(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); void NotifyBlockTransferAfter(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); + bool BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); void ReadFramebufferToMemory(VirtualFramebuffer *vfb, int x, int y, int w, int h); void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes); @@ -304,7 +308,10 @@ public: virtual void Resized(); virtual void DestroyAllFBOs(); - Draw::Framebuffer *GetTempFBO(TempFBO reason, u16 w, u16 h, Draw::FBColorDepth colorDepth = Draw::FBO_8888); + virtual void DeviceLost(); + virtual void DeviceRestore(Draw::DrawContext *draw); + + Draw::Framebuffer *GetTempFBO(TempFBO reason, u16 w, u16 h); // Debug features virtual bool GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxRes); @@ -315,6 +322,7 @@ public: const std::vector &Framebuffers() { return vfbs_; } + void ReinterpretFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old); protected: virtual void PackFramebufferSync_(VirtualFramebuffer *vfb, int x, int y, int w, int h); @@ -339,7 +347,6 @@ protected: void NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb, bool vfbFormatChanged); void NotifyRenderFramebufferSwitched(VirtualFramebuffer *prevVfb, VirtualFramebuffer *vfb, bool isClearingDepth); - virtual void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) = 0; void BlitFramebufferDepth(VirtualFramebuffer *src, VirtualFramebuffer *dst); void ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, int h, bool force = false, bool skipCopy = false); @@ -349,11 +356,9 @@ protected: void DownloadFramebufferOnSwitch(VirtualFramebuffer *vfb); void FindTransferFramebuffers(VirtualFramebuffer *&dstBuffer, VirtualFramebuffer *&srcBuffer, u32 dstBasePtr, int dstStride, int &dstX, int &dstY, u32 srcBasePtr, int srcStride, int &srcX, int &srcY, int &srcWidth, int &srcHeight, int &dstWidth, int &dstHeight, int bpp); VirtualFramebuffer *FindDownloadTempBuffer(VirtualFramebuffer *vfb); - virtual bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb); virtual void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) = 0; VirtualFramebuffer *CreateRAMFramebuffer(uint32_t fbAddress, int width, int height, int stride, GEBufferFormat format); - void OptimizeDownloadRange(VirtualFramebuffer *vfb, int &x, int &y, int &w, int &h); void UpdateFramebufUsage(VirtualFramebuffer *vfb); @@ -400,9 +405,10 @@ protected: bool gameUsesSequentialCopies_ = false; - // Sampled in BeginFrame for safety. + // Sampled in BeginFrame/UpdateSize for safety. float renderWidth_ = 0.0f; float renderHeight_ = 0.0f; + float renderScaleFactor_ = 1.0f; int pixelWidth_; int pixelHeight_; int bloomHack_ = 0; @@ -424,4 +430,11 @@ protected: FBO_OLD_AGE = 5, FBO_OLD_USAGE_FLAG = 15, }; + + // Thin3D stuff for reinterpreting image data between the various 16-bit formats. + // Safe, not optimal - there might be input attachment tricks, etc, but we can't use them + // since we don't want N different implementations. + Draw::Pipeline *reinterpretFromTo_[3][3]{}; + Draw::ShaderModule *reinterpretVS_ = nullptr; + Draw::SamplerState *reinterpretSampler_ = nullptr; }; diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 8ccf5b2764..83ce5b61ee 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -253,7 +253,7 @@ StencilValueType ReplaceAlphaWithStencilType() { return STENCIL_VALUE_KEEP; } -ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bufferFormat) { +ReplaceBlendType ReplaceBlendWithShader(bool allowFramebufferRead, GEBufferFormat bufferFormat) { if (!gstate.isAlphaBlendEnabled() || gstate.isModeClear()) { return REPLACE_BLEND_NO; } @@ -262,14 +262,14 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu // Let's get the non-factor modes out of the way first. switch (eq) { case GE_BLENDMODE_ABSDIFF: - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; case GE_BLENDMODE_MIN: case GE_BLENDMODE_MAX: if (gstate_c.Supports(GPU_SUPPORTS_BLEND_MINMAX)) { return REPLACE_BLEND_STANDARD; } else { - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; } default: @@ -292,19 +292,19 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu return REPLACE_BLEND_2X_ALPHA; // Can't double, we need the source color to be correct. // Doubling only alpha would clamp the src alpha incorrectly. - return !allowShaderBlend ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLEDSTALPHA: case GE_DSTBLEND_DOUBLEINVDSTALPHA: if (bufferFormat == GE_FORMAT_565) return REPLACE_BLEND_2X_ALPHA; - return !allowShaderBlend ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLESRCALPHA: // We can't technically do this correctly (due to clamping) without reading the dst color. // Using a copy isn't accurate either, though, when there's overlap. if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) - return !allowShaderBlend ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; return REPLACE_BLEND_PRE_SRC_2X_ALPHA; case GE_DSTBLEND_DOUBLEINVSRCALPHA: @@ -331,7 +331,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu return REPLACE_BLEND_STANDARD; } // Can't double, we need the source color to be correct. - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLEDSTALPHA: case GE_DSTBLEND_DOUBLEINVDSTALPHA: @@ -340,7 +340,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu // Doubling will have no effect here. return REPLACE_BLEND_STANDARD; } - return !allowShaderBlend ? REPLACE_BLEND_2X_SRC : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_SRC : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLESRCALPHA: case GE_DSTBLEND_DOUBLEINVSRCALPHA: @@ -349,7 +349,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu } // Double both src (for dst alpha) and alpha (for dst factor.) // But to be accurate (clamping), we need to read the dst color. - return !allowShaderBlend ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_SRCALPHA: case GE_DSTBLEND_INVSRCALPHA: @@ -361,7 +361,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu return REPLACE_BLEND_STANDARD; } // We can't technically do this correctly (due to clamping) without reading the dst alpha. - return !allowShaderBlend ? REPLACE_BLEND_2X_SRC : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_SRC : REPLACE_BLEND_COPY_FBO; } case GE_SRCBLEND_DOUBLEINVDSTALPHA: @@ -375,14 +375,14 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu if (bufferFormat == GE_FORMAT_565) { return REPLACE_BLEND_STANDARD; } - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLESRCALPHA: case GE_DSTBLEND_DOUBLEINVSRCALPHA: if (bufferFormat == GE_FORMAT_565) { return REPLACE_BLEND_2X_ALPHA; } - return !allowShaderBlend ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_SRCALPHA: case GE_DSTBLEND_INVSRCALPHA: @@ -393,7 +393,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu if (bufferFormat == GE_FORMAT_565) { return REPLACE_BLEND_STANDARD; } - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; } case GE_SRCBLEND_FIXA: @@ -401,7 +401,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu switch (funcB) { case GE_DSTBLEND_DOUBLESRCALPHA: // Can't safely double alpha, will clamp. - return !allowShaderBlend ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_DOUBLEINVSRCALPHA: // Doubling alpha is safe for the inverse, will clamp to zero either way. @@ -412,7 +412,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu if (bufferFormat == GE_FORMAT_565) { return REPLACE_BLEND_STANDARD; } - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; case GE_DSTBLEND_FIXB: default: @@ -446,14 +446,14 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu if (funcA == GE_SRCBLEND_SRCALPHA || funcA == GE_SRCBLEND_INVSRCALPHA) { // Can't safely double alpha, will clamp. However, a copy may easily be worse due to overlap. if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) - return !allowShaderBlend ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_PRE_SRC_2X_ALPHA : REPLACE_BLEND_COPY_FBO; return REPLACE_BLEND_PRE_SRC_2X_ALPHA; } else { // This means dst alpha/color is used in the src factor. // Unfortunately, copying here causes overlap problems in Silent Hill games (it seems?) // We will just hope that doubling alpha for the dst factor will not clamp too badly. if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) - return !allowShaderBlend ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_2X_ALPHA : REPLACE_BLEND_COPY_FBO; return REPLACE_BLEND_2X_ALPHA; } @@ -470,7 +470,7 @@ ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bu if (bufferFormat == GE_FORMAT_565) { return REPLACE_BLEND_STANDARD; } - return !allowShaderBlend ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; + return !allowFramebufferRead ? REPLACE_BLEND_STANDARD : REPLACE_BLEND_COPY_FBO; default: return REPLACE_BLEND_STANDARD; @@ -906,7 +906,7 @@ static void ApplyLogicOp(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq & } // Try to simulate some common logic ops. -void ApplyStencilReplaceAndLogicOp(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState) { +void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState) { StencilValueType stencilType = STENCIL_VALUE_KEEP; if (replaceAlphaWithStencil == REPLACE_ALPHA_YES) { stencilType = ReplaceAlphaWithStencilType(); @@ -956,9 +956,72 @@ void ApplyStencilReplaceAndLogicOp(ReplaceAlphaType replaceAlphaWithStencil, Gen } } -// Called even if AlphaBlendEnable == false - it also deals with stencil-related blend state. +bool IsColorWriteMaskComplex(bool allowFramebufferRead) { + // Restrict to Outrun temporarily (by uglily reusing the ReinterpretFramebuffers flag) + // This check must match the one in ConvertMaskState. + if (!allowFramebufferRead || !PSP_CoreParameter().compat.flags().ReinterpretFramebuffers) { + // Don't have a choice - we'll make do but it won't always be right. + return false; + } -void ConvertBlendState(GenericBlendState &blendState, bool allowShaderBlend) { + uint32_t colorMask = (gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24); + + for (int i = 0; i < 4; i++) { + switch (colorMask & 0xFF) { + case 0x0: + case 0xFF: + break; + default: + return true; + } + colorMask >>= 8; + } + return false; +} + +// If we can we emulate the colorMask by simply toggling the full R G B A masks offered +// by modern hardware, we do that. This is 99.9% of the time. +// When that's not enough, we fall back on a technique similar to shader blending, +// we read from the framebuffer (or a copy of it). +void ConvertMaskState(GenericMaskState &maskState, bool allowFramebufferRead) { + // Invert to convert masks from the PSP's format where 1 is don't draw to PC where 1 is draw. + uint32_t colorMask = ~((gstate.pmskc & 0xFFFFFF) | (gstate.pmska << 24)); + + maskState.applyFramebufferRead = false; + for (int i = 0; i < 4; i++) { + int channelMask = colorMask & 0xFF; + switch (channelMask) { + case 0x0: + maskState.rgba[i] = false; + break; + case 0xFF: + maskState.rgba[i] = true; + break; + default: + if (allowFramebufferRead) { + // Instead of just 'true', restrict shader bitmasks to Outrun temporarily (by uglily reusing the ReinterpretFramebuffers flag) + // TODO: This check must match the one in IsColorWriteMaskComplex. + maskState.applyFramebufferRead = PSP_CoreParameter().compat.flags().ReinterpretFramebuffers; + maskState.rgba[i] = true; + } else { + // Use the old heuristic. + maskState.rgba[i] = channelMask >= 128; + } + } + colorMask >>= 8; + } + + // Let's not write to alpha if stencil isn't enabled. + if (IsStencilTestOutputDisabled()) { + maskState.rgba[3] = false; + } else if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { + // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. + maskState.rgba[3] = false; + } +} + +// Called even if AlphaBlendEnable == false - it also deals with stencil-related blend state. +void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead) { // Blending is a bit complex to emulate. This is due to several reasons: // // * Doubled blend modes (src, dst, inversed) aren't supported in OpenGL. @@ -969,25 +1032,25 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowShaderBlend) { // // If we can't apply blending, we make a copy of the framebuffer and do it manually. - blendState.applyShaderBlending = false; - blendState.dirtyShaderBlend = false; + blendState.applyFramebufferRead = false; + blendState.dirtyShaderBlendFixValues = false; blendState.useBlendColor = false; blendState.replaceAlphaWithStencil = REPLACE_ALPHA_NO; - ReplaceBlendType replaceBlend = ReplaceBlendWithShader(allowShaderBlend, gstate.FrameBufFormat()); + ReplaceBlendType replaceBlend = ReplaceBlendWithShader(allowFramebufferRead, gstate.FrameBufFormat()); ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(replaceBlend); bool usePreSrc = false; switch (replaceBlend) { case REPLACE_BLEND_NO: - blendState.resetShaderBlending = true; + blendState.resetFramebufferRead = true; // We may still want to do something about stencil -> alpha. - ApplyStencilReplaceAndLogicOp(replaceAlphaWithStencil, blendState); + ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState); return; case REPLACE_BLEND_COPY_FBO: - blendState.applyShaderBlending = true; - blendState.resetShaderBlending = false; + blendState.applyFramebufferRead = true; + blendState.resetFramebufferRead = false; blendState.replaceAlphaWithStencil = replaceAlphaWithStencil; break; @@ -1003,7 +1066,7 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowShaderBlend) { } blendState.enabled = true; - blendState.resetShaderBlending = true; + blendState.resetFramebufferRead = true; const GEBlendMode blendFuncEq = gstate.getBlendEq(); GEBlendSrcFactor blendFuncA = gstate.getBlendFuncA(); @@ -1071,7 +1134,7 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowShaderBlend) { glBlendFuncA = BlendFactor::ONE; // Need to pull in the fixed color. TODO: If it hasn't changed, no need to dirty. if (blendFuncA == GE_SRCBLEND_FIXA) { - blendState.dirtyShaderBlend = true; + blendState.dirtyShaderBlendFixValues = true; } } diff --git a/GPU/Common/GPUStateUtils.h b/GPU/Common/GPUStateUtils.h index d58d38f6d4..943754c968 100644 --- a/GPU/Common/GPUStateUtils.h +++ b/GPU/Common/GPUStateUtils.h @@ -1,5 +1,6 @@ #pragma once +#include #include "Common/CommonTypes.h" #include "GPU/ge_constants.h" @@ -25,12 +26,18 @@ enum ReplaceAlphaType { }; enum ReplaceBlendType { - REPLACE_BLEND_NO, + REPLACE_BLEND_NO, // Blend function handled directly with blend states. + REPLACE_BLEND_STANDARD, + + // SRC part of blend function handled in-shader. REPLACE_BLEND_PRE_SRC, REPLACE_BLEND_PRE_SRC_2X_ALPHA, REPLACE_BLEND_2X_ALPHA, REPLACE_BLEND_2X_SRC, + + // Full blend equation runs in shader. + // We might have to make a copy of the framebuffer target to read from. REPLACE_BLEND_COPY_FBO, }; @@ -47,6 +54,9 @@ bool IsAlphaTestAgainstZero(); bool NeedsTestDiscard(); bool IsStencilTestOutputDisabled(); +// If not, we have to emulate it in the shader, similar to blend replace. +bool IsColorMaskSimple(uint32_t colorMask); + StencilValueType ReplaceAlphaWithStencilType(); ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend); ReplaceBlendType ReplaceBlendWithShader(bool allowShaderBlend, GEBufferFormat bufferFormat); @@ -121,9 +131,9 @@ enum class BlendEq : uint8_t { struct GenericBlendState { bool enabled; - bool resetShaderBlending; - bool applyShaderBlending; - bool dirtyShaderBlend; + bool resetFramebufferRead; + bool applyFramebufferRead; + bool dirtyShaderBlendFixValues; ReplaceAlphaType replaceAlphaWithStencil; BlendFactor srcColor; @@ -158,7 +168,16 @@ struct GenericBlendState { }; void ConvertBlendState(GenericBlendState &blendState, bool allowShaderBlend); -void ApplyStencilReplaceAndLogicOp(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState); +void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState); + +struct GenericMaskState { + bool applyFramebufferRead; + uint32_t uniformMask; // For each bit, opposite to the PSP. + bool rgba[4]; // true = draw, false = don't draw this channel +}; + +void ConvertMaskState(GenericMaskState &maskState, bool allowFramebufferRead); +bool IsColorWriteMaskComplex(bool allowFramebufferRead); struct GenericStencilFuncState { bool enabled; diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 5d62f1a7fe..01f698e5b7 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -33,7 +33,7 @@ #include "Core/HLE/sceDisplay.h" #include "GPU/Common/PostShader.h" #include "GPU/Common/PresentationCommon.h" -#include "GPU/Common/ShaderTranslation.h" +#include "Common/GPU/ShaderTranslation.h" struct Vertex { float x, y, z; @@ -240,8 +240,8 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha } std::string vsError, fsError; - Draw::ShaderModule *vs = CompileShaderModule(Draw::ShaderStage::VERTEX, GLSL_140, vsSourceGLSL, &vsError); - Draw::ShaderModule *fs = CompileShaderModule(Draw::ShaderStage::FRAGMENT, GLSL_140, fsSourceGLSL, &fsError); + Draw::ShaderModule *vs = CompileShaderModule(ShaderStage::Vertex, GLSL_1xx, vsSourceGLSL, &vsError); + Draw::ShaderModule *fs = CompileShaderModule(ShaderStage::Fragment, GLSL_1xx, fsSourceGLSL, &fsError); // Don't worry, CompileShaderModule makes sure they get freed if one succeeded. if (!fs || !vs) { @@ -253,13 +253,13 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha return false; } - Draw::UniformBufferDesc postShaderDesc{ sizeof(PostShaderUniforms), { - { "gl_HalfPixel", 0, -1, Draw::UniformType::FLOAT4, offsetof(PostShaderUniforms, gl_HalfPixel) }, - { "u_texelDelta", 1, 1, Draw::UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) }, - { "u_pixelDelta", 2, 2, Draw::UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) }, - { "u_time", 3, 3, Draw::UniformType::FLOAT4, offsetof(PostShaderUniforms, time) }, - { "u_setting", 4, 4, Draw::UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) }, - { "u_video", 5, 5, Draw::UniformType::FLOAT1, offsetof(PostShaderUniforms, video) }, + UniformBufferDesc postShaderDesc{ sizeof(PostShaderUniforms), { + { "gl_HalfPixel", 0, -1, UniformType::FLOAT4, offsetof(PostShaderUniforms, gl_HalfPixel) }, + { "u_texelDelta", 1, 1, UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) }, + { "u_pixelDelta", 2, 2, UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) }, + { "u_time", 3, 3, UniformType::FLOAT4, offsetof(PostShaderUniforms, time) }, + { "u_setting", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) }, + { "u_video", 5, 5, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) }, } }; Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc); if (!pipeline) @@ -317,7 +317,7 @@ bool PresentationCommon::AllocateFramebuffer(int w, int h) { } // No depth/stencil for post processing - Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, false, Draw::FBO_8888, "presentation" }); + Draw::Framebuffer *fbo = draw_->CreateFramebuffer({ w, h, 1, 1, false, "presentation" }); if (!fbo) { return false; } @@ -366,13 +366,13 @@ void PresentationCommon::DeviceRestore(Draw::DrawContext *draw) { CreateDeviceObjects(); } -Draw::Pipeline *PresentationCommon::CreatePipeline(std::vector shaders, bool postShader, const Draw::UniformBufferDesc *uniformDesc) { +Draw::Pipeline *PresentationCommon::CreatePipeline(std::vector shaders, bool postShader, const UniformBufferDesc *uniformDesc) { using namespace Draw; Semantic pos = SEM_POSITION; Semantic tc = SEM_TEXCOORD0; // Shader translation marks these both as "TEXCOORDs" on HLSL... - if (postShader && (lang_ == HLSL_D3D11 || lang_ == HLSL_D3D11_LEVEL9 || lang_ == HLSL_DX9)) { + if (postShader && (lang_ == HLSL_D3D11 || lang_ == HLSL_D3D9)) { pos = SEM_TEXCOORD0; tc = SEM_TEXCOORD1; } @@ -411,7 +411,7 @@ void PresentationCommon::CreateDeviceObjects() { vdata_ = draw_->CreateBuffer(sizeof(Vertex) * 8, BufferUsageFlag::DYNAMIC | BufferUsageFlag::VERTEXDATA); - // TODO: Use 4 and a strip? + // TODO: Use a triangle strip? Makes the UV rotation slightly more complex. idata_ = draw_->CreateBuffer(sizeof(uint16_t) * 6, BufferUsageFlag::DYNAMIC | BufferUsageFlag::INDEXDATA); uint16_t indexes[] = { 0, 1, 2, 0, 2, 3 }; draw_->UpdateBuffer(idata_, (const uint8_t *)indexes, 0, sizeof(indexes), Draw::UPDATE_DISCARD); @@ -465,7 +465,7 @@ void PresentationCommon::DestroyPostShader() { postShaderFBOUsage_.clear(); } -Draw::ShaderModule *PresentationCommon::CompileShaderModule(Draw::ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString) { +Draw::ShaderModule *PresentationCommon::CompileShaderModule(ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString) { std::string translated = src; bool translationFailed = false; if (lang != lang_) { @@ -476,30 +476,7 @@ Draw::ShaderModule *PresentationCommon::CompileShaderModule(Draw::ShaderStage st } } - Draw::ShaderLanguage mappedLang; - // These aren't exact, unfortunately, but we just need the type Draw will accept. - switch (lang_) { - case GLSL_140: - mappedLang = Draw::ShaderLanguage::GLSL_ES_200; - break; - case GLSL_300: - mappedLang = Draw::ShaderLanguage::GLSL_410; - break; - case GLSL_VULKAN: - mappedLang = Draw::ShaderLanguage::GLSL_VULKAN; - break; - case HLSL_DX9: - mappedLang = Draw::ShaderLanguage::HLSL_D3D9; - break; - case HLSL_D3D11: - case HLSL_D3D11_LEVEL9: - mappedLang = Draw::ShaderLanguage::HLSL_D3D11; - break; - default: - mappedLang = Draw::ShaderLanguage::GLSL_ES_200; - break; - } - Draw::ShaderModule *shader = draw_->CreateShaderModule(stage, mappedLang, (const uint8_t *)translated.c_str(), translated.size(), "postshader"); + Draw::ShaderModule *shader = draw_->CreateShaderModule(stage, lang_, (const uint8_t *)translated.c_str(), translated.size(), "postshader"); if (shader) postShaderModules_.push_back(shader); return shader; @@ -752,7 +729,7 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u draw_->BindPipeline(nullptr); } -void PresentationCommon::CalculateRenderResolution(int *width, int *height, bool *upscaling, bool *ssaa) { +void PresentationCommon::CalculateRenderResolution(int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) { // Check if postprocessing shader is doing upscaling as it requires native resolution std::vector shaderInfo; if (!g_Config.vPostShaderNames.empty() && g_Config.vPostShaderNames[0] != "Off") { @@ -799,4 +776,6 @@ void PresentationCommon::CalculateRenderResolution(int *width, int *height, bool *width = 480 * zoom; *height = 272 * zoom; } + + *scaleFactor = zoom; } diff --git a/GPU/Common/PresentationCommon.h b/GPU/Common/PresentationCommon.h index 7acce4083a..c3f56699f9 100644 --- a/GPU/Common/PresentationCommon.h +++ b/GPU/Common/PresentationCommon.h @@ -17,7 +17,7 @@ #pragma once -#include "GPU/Common/ShaderCommon.h" +#include "Common/GPU/Shader.h" struct CardboardSettings { bool enabled; @@ -100,7 +100,7 @@ public: void SourceFramebuffer(Draw::Framebuffer *fb, int bufferWidth, int bufferHeight); void CopyToOutput(OutputFlags flags, int uvRotation, float u0, float v0, float u1, float v1); - void CalculateRenderResolution(int *width, int *height, bool *upscaling, bool *ssaa); + void CalculateRenderResolution(int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa); protected: void CreateDeviceObjects(); @@ -109,8 +109,8 @@ protected: void ShowPostShaderError(const std::string &errorString); - Draw::ShaderModule *CompileShaderModule(Draw::ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString); - Draw::Pipeline *CreatePipeline(std::vector shaders, bool postShader, const Draw::UniformBufferDesc *uniformDesc); + Draw::ShaderModule *CompileShaderModule(ShaderStage stage, ShaderLanguage lang, const std::string &src, std::string *errorString); + Draw::Pipeline *CreatePipeline(std::vector shaders, bool postShader, const UniformBufferDesc *uniformDesc); bool BuildPostShader(const ShaderInfo *shaderInfo, const ShaderInfo *next); bool AllocateFramebuffer(int w, int h); diff --git a/GPU/Common/ReinterpretFramebuffer.cpp b/GPU/Common/ReinterpretFramebuffer.cpp new file mode 100644 index 0000000000..4793278f03 --- /dev/null +++ b/GPU/Common/ReinterpretFramebuffer.cpp @@ -0,0 +1,86 @@ +#include + +#include "Common/GPU/Shader.h" +#include "Common/GPU/ShaderWriter.h" +#include "GPU/Common/ReinterpretFramebuffer.h" + +static const VaryingDef varyings[1] = { + { "vec2", "v_texcoord", "TEXCOORD0", 0, "highp" }, +}; + +// TODO: We could possibly have an option to preserve any extra color precision? But gonna start without it. +// Requires full size integer math. It would be possible to make a floating point-only version with lots of +// modulo and stuff, might do it one day. +bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBufferFormat to, const ShaderLanguageDesc &lang) { + if (!lang.bitwiseOps) { + return false; + } + + ShaderWriter writer(buffer, lang, ShaderStage::Fragment, nullptr, 0); + + writer.HighPrecisionFloat(); + + writer.DeclareSampler2D("samp", 0); + writer.DeclareTexture2D("tex", 0); + + writer.BeginFSMain(Slice::empty(), varyings); + + writer.C(" vec4 val = ").SampleTexture2D("tex", "samp", "v_texcoord.xy").C(";\n"); + + switch (from) { + case GE_FORMAT_4444: + writer.C(" uint color = uint(val.r * 15.99) | (uint(val.g * 15.99) << 4) | (uint(val.b * 15.99) << 8) | (uint(val.a * 15.99) << 12);\n"); + break; + case GE_FORMAT_5551: + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 31.99) << 5) | (uint(val.b * 31.99) << 10);\n"); + writer.C(" if (val.a >= 0.5) color |= 0x8000U;\n"); + break; + case GE_FORMAT_565: + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 63.99) << 5) | (uint(val.b * 31.99) << 11);\n"); + break; + default: + _assert_(false); + break; + } + + switch (to) { + case GE_FORMAT_4444: + writer.C(" vec4 outColor = vec4(float(color & 0xFU), float((color >> 4) & 0xFU), float((color >> 8) & 0xFU), float((color >> 12) & 0xFU));\n"); + writer.C(" outColor *= 1.0 / 15.0;\n"); + break; + case GE_FORMAT_5551: + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x1FU), float((color >> 10) & 0x1FU), 0.0);\n"); + writer.C(" outColor.rgb *= 1.0 / 31.0;\n"); + writer.C(" outColor.a = float(color >> 15);\n"); + break; + case GE_FORMAT_565: + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x3FU), float((color >> 11) & 0x1FU), 1.0);\n"); + writer.C(" outColor.rb *= 1.0 / 31.0;\n"); + writer.C(" outColor.g *= 1.0 / 63.0;\n"); + break; + default: + _assert_(false); + break; + } + + writer.EndFSMain("outColor"); + return true; +} + +bool GenerateReinterpretVertexShader(char *buffer, const ShaderLanguageDesc &lang) { + if (!lang.bitwiseOps) { + return false; + } + ShaderWriter writer(buffer, lang, ShaderStage::Vertex, nullptr, 0); + + writer.BeginVSMain(Slice::empty(), Slice::empty(), varyings); + + writer.C(" float x = -1.0 + float((gl_VertexIndex & 1) << 2);\n"); + writer.C(" float y = -1.0 + float((gl_VertexIndex & 2) << 1);\n"); + writer.C(" v_texcoord = (vec2(x, y) + vec2(1.0, 1.0)) * 0.5;\n"); + writer.F(" y *= %s1.0;\n", lang.viewportYSign); + writer.C(" gl_Position = vec4(x, y, 0.0, 1.0);\n"); + + writer.EndVSMain(varyings); + return true; +} diff --git a/GPU/Common/ReinterpretFramebuffer.h b/GPU/Common/ReinterpretFramebuffer.h new file mode 100644 index 0000000000..14b8b042a9 --- /dev/null +++ b/GPU/Common/ReinterpretFramebuffer.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Common/Log.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUCommon.h" +#include "Common/GPU/ShaderWriter.h" + +bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBufferFormat to, const ShaderLanguageDesc &lang); + +// Just a single one. Can probably be shared with a lot of similar use cases. +// Generates the coordinates for a fullscreen triangle. +bool GenerateReinterpretVertexShader(char *buffer, const ShaderLanguageDesc &lang); diff --git a/GPU/Common/ShaderCommon.cpp b/GPU/Common/ShaderCommon.cpp index 1cbc7d3be5..ea8bf916f2 100644 --- a/GPU/Common/ShaderCommon.cpp +++ b/GPU/Common/ShaderCommon.cpp @@ -1,101 +1,2 @@ -#ifdef USE_CRT_DBG -#undef new -#endif - - -#include "ext/glslang/SPIRV/GlslangToSpv.h" - -void init_resources(TBuiltInResource &Resources) { - Resources.maxLights = 32; - Resources.maxClipPlanes = 6; - Resources.maxTextureUnits = 32; - Resources.maxTextureCoords = 32; - Resources.maxVertexAttribs = 64; - Resources.maxVertexUniformComponents = 4096; - Resources.maxVaryingFloats = 64; - Resources.maxVertexTextureImageUnits = 32; - Resources.maxCombinedTextureImageUnits = 80; - Resources.maxTextureImageUnits = 32; - Resources.maxFragmentUniformComponents = 4096; - Resources.maxDrawBuffers = 32; - Resources.maxVertexUniformVectors = 128; - Resources.maxVaryingVectors = 8; - Resources.maxFragmentUniformVectors = 16; - Resources.maxVertexOutputVectors = 16; - Resources.maxFragmentInputVectors = 15; - Resources.minProgramTexelOffset = -8; - Resources.maxProgramTexelOffset = 7; - Resources.maxClipDistances = 8; - Resources.maxComputeWorkGroupCountX = 65535; - Resources.maxComputeWorkGroupCountY = 65535; - Resources.maxComputeWorkGroupCountZ = 65535; - Resources.maxComputeWorkGroupSizeX = 1024; - Resources.maxComputeWorkGroupSizeY = 1024; - Resources.maxComputeWorkGroupSizeZ = 64; - Resources.maxComputeUniformComponents = 1024; - Resources.maxComputeTextureImageUnits = 16; - Resources.maxComputeImageUniforms = 8; - Resources.maxComputeAtomicCounters = 8; - Resources.maxComputeAtomicCounterBuffers = 1; - Resources.maxVaryingComponents = 60; - Resources.maxVertexOutputComponents = 64; - Resources.maxGeometryInputComponents = 64; - Resources.maxGeometryOutputComponents = 128; - Resources.maxFragmentInputComponents = 128; - Resources.maxImageUnits = 8; - Resources.maxCombinedImageUnitsAndFragmentOutputs = 8; - Resources.maxCombinedShaderOutputResources = 8; - Resources.maxImageSamples = 0; - Resources.maxVertexImageUniforms = 0; - Resources.maxTessControlImageUniforms = 0; - Resources.maxTessEvaluationImageUniforms = 0; - Resources.maxGeometryImageUniforms = 0; - Resources.maxFragmentImageUniforms = 8; - Resources.maxCombinedImageUniforms = 8; - Resources.maxGeometryTextureImageUnits = 16; - Resources.maxGeometryOutputVertices = 256; - Resources.maxGeometryTotalOutputComponents = 1024; - Resources.maxGeometryUniformComponents = 1024; - Resources.maxGeometryVaryingComponents = 64; - Resources.maxTessControlInputComponents = 128; - Resources.maxTessControlOutputComponents = 128; - Resources.maxTessControlTextureImageUnits = 16; - Resources.maxTessControlUniformComponents = 1024; - Resources.maxTessControlTotalOutputComponents = 4096; - Resources.maxTessEvaluationInputComponents = 128; - Resources.maxTessEvaluationOutputComponents = 128; - Resources.maxTessEvaluationTextureImageUnits = 16; - Resources.maxTessEvaluationUniformComponents = 1024; - Resources.maxTessPatchComponents = 120; - Resources.maxPatchVertices = 32; - Resources.maxTessGenLevel = 64; - Resources.maxViewports = 16; - Resources.maxVertexAtomicCounters = 0; - Resources.maxTessControlAtomicCounters = 0; - Resources.maxTessEvaluationAtomicCounters = 0; - Resources.maxGeometryAtomicCounters = 0; - Resources.maxFragmentAtomicCounters = 8; - Resources.maxCombinedAtomicCounters = 8; - Resources.maxAtomicCounterBindings = 1; - Resources.maxVertexAtomicCounterBuffers = 0; - Resources.maxTessControlAtomicCounterBuffers = 0; - Resources.maxTessEvaluationAtomicCounterBuffers = 0; - Resources.maxGeometryAtomicCounterBuffers = 0; - Resources.maxFragmentAtomicCounterBuffers = 1; - Resources.maxCombinedAtomicCounterBuffers = 1; - Resources.maxAtomicCounterBufferSize = 16384; - Resources.maxTransformFeedbackBuffers = 4; - Resources.maxTransformFeedbackInterleavedComponents = 64; - Resources.maxCullDistances = 8; - Resources.maxCombinedClipAndCullDistances = 8; - Resources.maxSamples = 4; - Resources.limits.nonInductiveForLoops = 1; - Resources.limits.whileLoops = 1; - Resources.limits.doWhileLoops = 1; - Resources.limits.generalUniformIndexing = 1; - Resources.limits.generalAttributeMatrixVectorIndexing = 1; - Resources.limits.generalVaryingIndexing = 1; - Resources.limits.generalSamplerIndexing = 1; - Resources.limits.generalVariableIndexing = 1; - Resources.limits.generalConstantMatrixVectorIndexing = 1; -} \ No newline at end of file +#include "Common/GPU/Shader.h" +#include "GPU/Common/ShaderCommon.h" diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h index 3280c765b6..d45f43bc06 100644 --- a/GPU/Common/ShaderCommon.h +++ b/GPU/Common/ShaderCommon.h @@ -18,20 +18,12 @@ #pragma once #include +#include namespace Draw { class DrawContext; } -enum ShaderLanguage { - GLSL_140, - GLSL_300, - GLSL_VULKAN, - HLSL_DX9, - HLSL_D3D11, - HLSL_D3D11_LEVEL9, -}; - enum DebugShaderType { SHADER_TYPE_VERTEX = 0, SHADER_TYPE_FRAGMENT = 1, @@ -49,7 +41,7 @@ enum DebugShaderStringType { }; // Shared between the backends. Not all are necessarily used by each backend, but this lets us share -// more code than before. +// more code than before. TODO: Can probably cut the number of these down without too much slowdown. enum : uint64_t { DIRTY_PROJMATRIX = 1ULL << 0, DIRTY_PROJTHROUGHMATRIX = 1ULL << 1, @@ -94,12 +86,13 @@ enum : uint64_t { DIRTY_CULLRANGE = 1ULL << 34, DIRTY_DEPAL = 1ULL << 35, + DIRTY_COLORWRITEMASK = 1ULL << 36, - // space for 5 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS. + // space for 4 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS. DIRTY_BONE_UNIFORMS = 0xFF000000ULL, - DIRTY_ALL_UNIFORMS = 0xFFFFFFFFFULL, + DIRTY_ALL_UNIFORMS = 0x1FFFFFFFFFULL, DIRTY_ALL_LIGHTS = DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3, // Other dirty elements that aren't uniforms! @@ -129,5 +122,37 @@ protected: Draw::DrawContext *draw_ = nullptr; }; +enum DoLightComputation { + LIGHT_OFF, + LIGHT_SHADE, + LIGHT_FULL, +}; + +// PSP vertex format. +enum class PspAttributeLocation { + POSITION = 0, + TEXCOORD = 1, + NORMAL = 2, + W1 = 3, + W2 = 4, + COLOR0 = 5, + COLOR1 = 6, + + COUNT +}; + +// Pre-fetched attrs and uniforms (used by GL only). +enum { + ATTR_POSITION = 0, + ATTR_TEXCOORD = 1, + ATTR_NORMAL = 2, + ATTR_W1 = 3, + ATTR_W2 = 4, + ATTR_COLOR0 = 5, + ATTR_COLOR1 = 6, + + ATTR_COUNT, +}; + struct TBuiltInResource; void init_resources(TBuiltInResource &Resources); diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 3f5864161a..b01c46044f 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -23,12 +23,12 @@ std::string VertexShaderDesc(const VShaderID &id) { if (id.Bit(VS_BIT_ENABLE_FOG)) desc << "Fog "; if (id.Bit(VS_BIT_NORM_REVERSE)) desc << "RevN "; if (id.Bit(VS_BIT_DO_TEXTURE)) desc << "Tex "; - if (id.Bit(VS_BIT_DO_TEXTURE_TRANSFORM)) { + int uvgMode = id.Bits(VS_BIT_UVGEN_MODE, 2); + if (uvgMode == GE_TEXMAP_TEXTURE_MATRIX) { int uvprojMode = id.Bits(VS_BIT_UVPROJ_MODE, 2); const char *uvprojModes[4] = { "TexProjPos ", "TexProjUV ", "TexProjNNrm ", "TexProjNrm " }; desc << uvprojModes[uvprojMode]; } - int uvgMode = id.Bits(VS_BIT_UVGEN_MODE, 2); const char *uvgModes[4] = { "UV ", "UVMtx ", "UVEnv ", "UVUnk " }; int ls0 = id.Bits(VS_BIT_LS0, 2); int ls1 = id.Bits(VS_BIT_LS1, 2); @@ -63,7 +63,6 @@ std::string VertexShaderDesc(const VShaderID &id) { void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation) { bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0; bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); - bool doTextureTransform = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP); bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT && !gstate.isModeClear(); @@ -85,7 +84,6 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, if (doTexture) { id.SetBit(VS_BIT_DO_TEXTURE); - id.SetBit(VS_BIT_DO_TEXTURE_TRANSFORM, doTextureTransform); } if (useHWTransform) { @@ -96,7 +94,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, id.SetBits(VS_BIT_UVGEN_MODE, 2, gstate.getUVGenMode()); // The next bits are used differently depending on UVgen mode - if (doTextureTransform) { + if (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX) { id.SetBits(VS_BIT_UVPROJ_MODE, 2, gstate.getUVProjMode()); } else if (doShadeMapping) { id.SetBits(VS_BIT_LS0, 2, gstate.getUVLS0()); @@ -173,6 +171,7 @@ std::string FragmentShaderDesc(const FShaderID &id) { 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_COLOR_WRITEMASK)) desc << "WriteMask "; if (id.Bit(FS_BIT_SHADER_TEX_CLAMP)) { desc << "TClamp"; if (id.Bit(FS_BIT_CLAMP_S)) desc << "S"; @@ -241,8 +240,11 @@ void ComputeFragmentShaderID(FShaderID *id_out, const Draw::Bugs &bugs) { bool doTextureAlpha = gstate.isTextureAlphaUsed(); bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT; bool useShaderDepal = gstate_c.useShaderDepal; + bool colorWriteMask = IsColorWriteMaskComplex(gstate_c.allowFramebufferRead); - ReplaceBlendType replaceBlend = ReplaceBlendWithShader(gstate_c.allowShaderBlend, gstate.FrameBufFormat()); + // Note how we here recompute some of the work already done in state mapping. + // Not ideal! At least we share the code. + ReplaceBlendType replaceBlend = ReplaceBlendWithShader(gstate_c.allowFramebufferRead, gstate.FrameBufFormat()); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(replaceBlend); // All texfuncs except replace are the same for RGB as for RGBA with full alpha. @@ -263,6 +265,7 @@ void ComputeFragmentShaderID(FShaderID *id_out, const Draw::Bugs &bugs) { id.SetBit(FS_BIT_TEXTURE_AT_OFFSET, textureAtOffset); } id.SetBit(FS_BIT_BGRA_TEXTURE, gstate_c.bgraTexture); + id.SetBit(FS_BIT_SHADER_DEPAL, useShaderDepal); } id.SetBit(FS_BIT_LMODE, lmode); @@ -308,7 +311,7 @@ void ComputeFragmentShaderID(FShaderID *id_out, const Draw::Bugs &bugs) { } id.SetBit(FS_BIT_FLATSHADE, doFlatShading); - id.SetBit(FS_BIT_SHADER_DEPAL, useShaderDepal); + id.SetBit(FS_BIT_COLOR_WRITEMASK, colorWriteMask); if (g_Config.bVendorBugChecksEnabled) { if (bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL)) { diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 3adc7f4c2b..5ae71487ad 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -16,7 +16,7 @@ enum VShaderBit : uint8_t { VS_BIT_HAS_COLOR = 3, VS_BIT_DO_TEXTURE = 4, // 5 is free. - VS_BIT_DO_TEXTURE_TRANSFORM = 6, + // 6 is free, // 7 is free. VS_BIT_USE_HW_TRANSFORM = 8, VS_BIT_HAS_NORMAL = 9, // conditioned on hw transform @@ -82,9 +82,9 @@ enum FShaderBit : uint8_t { FS_BIT_DO_TEXTURE_PROJ = 22, FS_BIT_COLOR_DOUBLE = 23, FS_BIT_STENCIL_TO_ALPHA = 24, // 2 bits - FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE = 26, // 4 bits + FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE = 26, // 4 bits (ReplaceAlphaType) FS_BIT_REPLACE_LOGIC_OP_TYPE = 30, // 2 bits - FS_BIT_REPLACE_BLEND = 32, // 3 bits + FS_BIT_REPLACE_BLEND = 32, // 3 bits (ReplaceBlendType) FS_BIT_BLENDEQ = 35, // 3 bits FS_BIT_BLENDFUNC_A = 38, // 4 bits FS_BIT_BLENDFUNC_B = 42, // 4 bits @@ -92,7 +92,7 @@ enum FShaderBit : uint8_t { FS_BIT_BGRA_TEXTURE = 47, FS_BIT_TEST_DISCARD_TO_ZERO = 48, FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL = 49, - // 50+ are free. + FS_BIT_COLOR_WRITEMASK = 50, }; static inline FShaderBit operator +(FShaderBit bit, int i) { @@ -159,13 +159,14 @@ protected: void SetBit(int bit, bool value = true) { if (value) { d[bit >> 5] |= 1 << (bit & 31); + } else { + d[bit >> 5] &= ~(1 << (bit & 31)); } } void SetBits(int bit, int count, int value) { - if (value != 0) { - const int mask = (1 << count) - 1; - d[bit >> 5] |= (value & mask) << (bit & 31); - } + const int mask = (1 << count) - 1; + const int shifted_mask = mask << (bit & 31); + d[bit >> 5] = (d[bit >> 5] & ~shifted_mask) | ((value & mask) << (bit & 31)); } }; diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 46f6a93855..bf2b540db5 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -203,6 +203,10 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView Uint8x3ToFloat4_AlphaUint8(ub->matAmbient, gstate.materialambient, gstate.getMaterialAmbientA()); } + if (dirtyUniforms & DIRTY_COLORWRITEMASK) { + ub->colorWriteMask = ~((gstate.pmska << 24) | (gstate.pmskc & 0xFFFFFF)); + } + // Texturing if (dirtyUniforms & DIRTY_UVSCALEOFFSET) { const float invW = 1.0f / (float)gstate_c.curTextureWidth; diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 5915297818..0959be328b 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -10,7 +10,7 @@ enum : uint64_t { DIRTY_BASE_UNIFORMS = DIRTY_WORLDMATRIX | DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWMATRIX | DIRTY_TEXMATRIX | DIRTY_ALPHACOLORREF | DIRTY_PROJMATRIX | DIRTY_FOGCOLOR | DIRTY_FOGCOEF | DIRTY_TEXENV | DIRTY_STENCILREPLACEVALUE | - DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA | + DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_COLORWRITEMASK | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA | DIRTY_BEZIERSPLINE | DIRTY_DEPAL, DIRTY_LIGHT_UNIFORMS = DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 | @@ -30,10 +30,10 @@ struct UB_VS_FS_Base { float depthRange[4]; float fogCoef[2]; float stencil; float pad0; float matAmbient[4]; - uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. - int pad2; int pad3; float cullRangeMin[4]; float cullRangeMax[4]; + uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. + uint32_t colorWriteMask; int pad3; // Fragment data float fogColor[4]; float texEnvColor[4]; @@ -46,58 +46,30 @@ struct UB_VS_FS_Base { }; static const char *ub_baseStr = -R"( mat4 proj_mtx; - mat4 proj_through_mtx; - mat3x4 view_mtx; - mat3x4 world_mtx; - mat3x4 tex_mtx; - vec4 uvscaleoffset; - vec4 depthRange; - vec2 fogcoef; - float stencilReplace; - vec4 matambientalpha; - uint spline_counts; - uint depal_mask_shift_off_fmt; - int pad2; - int pad3; - vec4 cullRangeMin; - vec4 cullRangeMax; - vec3 fogcolor; - vec3 texenv; - ivec4 alphacolorref; - ivec4 alphacolormask; - vec3 blendFixA; - vec3 blendFixB; - vec4 texclamp; - vec2 texclampoff; -)"; - -// HLSL code is shared so these names are changed to match those in DX9. -static const char *cb_baseStr = -R"( float4x4 u_proj; - float4x4 u_proj_through; - float4x3 u_view; - float4x3 u_world; - float4x3 u_tex; - float4 u_uvscaleoffset; - float4 u_depthRange; - float2 u_fogcoef; +R"( mat4 u_proj; + mat4 u_proj_through; + mat3x4 u_view; + mat3x4 u_world; + mat3x4 u_texmtx; + vec4 u_uvscaleoffset; + vec4 u_depthRange; + vec2 u_fogcoef; float u_stencilReplaceValue; - float4 u_matambientalpha; + vec4 u_matambientalpha; + vec4 u_cullRangeMin; + vec4 u_cullRangeMax; uint u_spline_counts; uint u_depal_mask_shift_off_fmt; - int pad2; - int pad3; - float4 u_cullRangeMin; - float4 u_cullRangeMax; - float3 u_fogcolor; - float3 u_texenv; - uint4 u_alphacolorref; - uint4 u_alphacolormask; - float3 u_blendFixA; - float3 u_blendFixB; - float4 u_texclamp; - float2 u_texclampoff; + uint u_colorWriteMask; + int u_pad3; + vec3 u_fogcolor; + vec3 u_texenv; + ivec4 u_alphacolorref; + ivec4 u_alphacolormask; + vec3 u_blendFixA; uint colorWriteMask; + vec3 u_blendFixB; + vec4 u_texclamp; + vec2 u_texclampoff; )"; // 512 bytes. Would like to shrink more. Some colors only have 8-bit precision and we expand @@ -118,67 +90,47 @@ struct UB_VS_Lights { static const char *ub_vs_lightsStr = R"( vec4 u_ambient; - vec3 matdiffuse; - vec4 matspecular; - vec3 matemissive; - vec3 pos[4]; - vec3 dir[4]; - vec3 att[4]; - vec2 angle_spotCoef[4]; - vec3 ambient[4]; - vec3 diffuse[4]; - vec3 specular[4]; -)"; - -// HLSL code is shared so these names are changed to match those in DX9. -static const char *cb_vs_lightsStr = -R"( float4 u_ambient; - float3 u_matdiffuse; - float4 u_matspecular; - float3 u_matemissive; - float3 u_lightpos0; - float3 u_lightpos1; - float3 u_lightpos2; - float3 u_lightpos3; - float3 u_lightdir0; - float3 u_lightdir1; - float3 u_lightdir2; - float3 u_lightdir3; - float3 u_lightatt0; - float3 u_lightatt1; - float3 u_lightatt2; - float3 u_lightatt3; - float4 u_lightangle_spotCoef0; - float4 u_lightangle_spotCoef1; - float4 u_lightangle_spotCoef2; - float4 u_lightangle_spotCoef3; - float3 u_lightambient0; - float3 u_lightambient1; - float3 u_lightambient2; - float3 u_lightambient3; - float3 u_lightdiffuse0; - float3 u_lightdiffuse1; - float3 u_lightdiffuse2; - float3 u_lightdiffuse3; - float3 u_lightspecular0; - float3 u_lightspecular1; - float3 u_lightspecular2; - float3 u_lightspecular3; + vec3 u_matdiffuse; + vec4 u_matspecular; + vec3 u_matemissive; + vec3 u_lightpos0; + vec3 u_lightpos1; + vec3 u_lightpos2; + vec3 u_lightpos3; + vec3 u_lightdir0; + vec3 u_lightdir1; + vec3 u_lightdir2; + vec3 u_lightdir3; + vec3 u_lightatt0; + vec3 u_lightatt1; + vec3 u_lightatt2; + vec3 u_lightatt3; + vec4 u_lightangle_spotCoef0; + vec4 u_lightangle_spotCoef1; + vec4 u_lightangle_spotCoef2; + vec4 u_lightangle_spotCoef3; + vec3 u_lightambient0; + vec3 u_lightambient1; + vec3 u_lightambient2; + vec3 u_lightambient3; + vec3 u_lightdiffuse0; + vec3 u_lightdiffuse1; + vec3 u_lightdiffuse2; + vec3 u_lightdiffuse3; + vec3 u_lightspecular0; + vec3 u_lightspecular1; + vec3 u_lightspecular2; + vec3 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. +// bones are being used. This is 384b. 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]; +R"( mat3x4 u_bone0; mat3x4 u_bone1; mat3x4 u_bone2; mat3x4 u_bone3; mat3x4 u_bone4; mat3x4 u_bone5; mat3x4 u_bone6; mat3x4 u_bone7; mat3x4 u_bone8; )"; void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bool hasNegZ); diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 3c0929ac41..fb2a7b0a68 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -487,9 +487,10 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { DeleteTexture(entryIter); } + const AttachCandidate &candidate = candidates[index]; nextTexture_ = nullptr; nextNeedsRebuild_ = false; - SetTextureFramebuffer(candidates[index]); + SetTextureFramebuffer(candidate); return nullptr; } } @@ -498,15 +499,14 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { if (!entry) { VERBOSE_LOG(G3D, "No texture in cache for %08x, decoding...", texaddr); - TexCacheEntry *entryNew = new TexCacheEntry{}; - cache_[cachekey].reset(entryNew); + entry = new TexCacheEntry{}; + cache_[cachekey].reset(entry); if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); } - entry = entryNew; - if (Memory::IsKernelAddress(texaddr)) { + if (Memory::IsKernelAndNotVolatileAddress(texaddr)) { // It's the builtin font texture. entry->status = TexCacheEntry::STATUS_RELIABLE; } else if (g_Config.bTextureBackoffCache) { @@ -570,6 +570,10 @@ std::vector TextureCacheCommon::GetFramebufferCandidates(const std::vector candidates; FramebufferNotificationChannel channel = Memory::IsDepthTexVRAMAddress(entry.addr) ? FramebufferNotificationChannel::NOTIFY_FB_DEPTH : FramebufferNotificationChannel::NOTIFY_FB_COLOR; + if (channel == FramebufferNotificationChannel::NOTIFY_FB_DEPTH && !gstate_c.Supports(GPU_SUPPORTS_DEPTH_TEXTURE)) { + // Depth texture not supported. Don't try to match it, fall back to the memory behind.. + return std::vector(); + } const std::vector &framebuffers = framebufferManager_->Framebuffers(); @@ -851,11 +855,19 @@ FramebufferMatchInfo TextureCacheCommon::MatchFramebuffer( WARN_LOG_ONCE(diffStrides1, G3D, "Texturing from framebuffer with different strides %d != %d", entry.bufw, framebuffer->fb_stride); } // NOTE: This check is okay because the first texture formats are the same as the buffer formats. - if (entry.format != (GETextureFormat)framebuffer->format) { - WARN_LOG_ONCE(diffFormat1, G3D, "Texturing from framebuffer with different formats %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format)); - return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH }; + if (IsTextureFormatBufferCompatible(entry.format)) { + if (TextureFormatMatchesBufferFormat(entry.format, framebuffer->format)) { + return FramebufferMatchInfo{ FramebufferMatch::VALID }; + } else if (IsTextureFormat16Bit(entry.format) && IsBufferFormat16Bit(framebuffer->format)) { + WARN_LOG_ONCE(diffFormat1, G3D, "Texturing from framebuffer with reinterpretable format: %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format)); + return FramebufferMatchInfo{ FramebufferMatch::VALID, 0, 0, true, TextureFormatToBufferFormat(entry.format) }; + } else { + WARN_LOG_ONCE(diffFormat2, G3D, "Texturing from framebuffer with incompatible formats %s != %s", GeTextureFormatToString(entry.format), GeBufferFormatToString(framebuffer->format)); + return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH }; + } } else { - return FramebufferMatchInfo{ FramebufferMatch::VALID }; + // Format incompatible, ignoring without comment. (maybe some really gnarly hacks will end up here...) + return FramebufferMatchInfo{ FramebufferMatch::NO_MATCH }; } } else { // Apply to buffered mode only. @@ -946,6 +958,13 @@ void TextureCacheCommon::SetTextureFramebuffer(const AttachCandidate &candidate) VirtualFramebuffer *framebuffer = candidate.fb; FramebufferMatchInfo fbInfo = candidate.match; + if (candidate.match.reinterpret) { + // TODO: Kinda ugly, maybe switch direction of the call? + GEBufferFormat oldFormat = candidate.fb->format; + candidate.fb->format = candidate.match.reinterpretTo; + framebufferManager_->ReinterpretFramebufferFrom(candidate.fb, oldFormat); + } + _dbg_assert_msg_(framebuffer != nullptr, "Framebuffer must not be null."); framebuffer->usageFlags |= FB_USAGE_TEXTURE; diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 6db18d08d3..beb1a6b6e0 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -212,6 +212,8 @@ struct FramebufferMatchInfo { FramebufferMatch match; u32 xOffset; u32 yOffset; + bool reinterpret; + GEBufferFormat reinterpretTo; }; struct AttachCandidate { diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp new file mode 100644 index 0000000000..37fd9403e6 --- /dev/null +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -0,0 +1,1111 @@ +// Copyright (c) 2012- 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/. + +#include +#include +#include + +#include "Common/StringUtils.h" +#include "Common/GPU/OpenGL/GLFeatures.h" +#include "Common/GPU/ShaderWriter.h" +#include "Core/Config.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUState.h" +#include "GPU/Common/ShaderId.h" +#include "GPU/Common/ShaderUniforms.h" +#include "GPU/Common/VertexDecoderCommon.h" +#include "GPU/Common/VertexShaderGenerator.h" + +#undef WRITE + +#define WRITE(p, ...) p.F(__VA_ARGS__) + +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", +}; + +const char *boneWeightAttrDeclHLSL[9] = { + "#ERROR boneWeightAttrDecl#\n", + "float a_w1:TEXCOORD1;\n", + "vec2 a_w1:TEXCOORD1;\n", + "vec3 a_w1:TEXCOORD1;\n", + "vec4 a_w1:TEXCOORD1;\n", + "vec4 a_w1:TEXCOORD1;\n float a_w2:TEXCOORD2;\n", + "vec4 a_w1:TEXCOORD1;\n vec2 a_w2:TEXCOORD2;\n", + "vec4 a_w1:TEXCOORD1;\n vec3 a_w2:TEXCOORD2;\n", + "vec4 a_w1:TEXCOORD1;\n vec4 a_w2:TEXCOORD2;\n", +}; + +const char *boneWeightAttrInitHLSL[9] = { + " #ERROR#\n", + " vec4 w1 = vec4(In.a_w1, 0.0, 0.0, 0.0);\n", + " vec4 w1 = vec4(In.a_w1.xy, 0.0, 0.0);\n", + " vec4 w1 = vec4(In.a_w1.xyz, 0.0);\n", + " vec4 w1 = In.a_w1;\n", + " vec4 w1 = In.a_w1;\n vec4 w2 = vec4(In.a_w2, 0.0, 0.0, 0.0);\n", + " vec4 w1 = In.a_w1;\n vec4 w2 = vec4(In.a_w2.xy, 0.0, 0.0);\n", + " vec4 w1 = In.a_w1;\n vec4 w2 = vec4(In.a_w2.xyz, 0.0);\n", + " vec4 w1 = In.a_w1;\n vec4 w2 = In.a_w2;\n", +}; + +// Depth range and viewport +// +// After the multiplication with the projection matrix, we have a 4D vector in clip space. +// In OpenGL, Z is from -1 to 1, while in D3D, Z is from 0 to 1. +// PSP appears to use the OpenGL convention. As Z is from -1 to 1, and the viewport is represented +// by a center and a scale, to find the final Z value, all we need to do is to multiply by ZScale and +// add ZCenter - these are properly scaled to directly give a Z value in [0, 65535]. +// +// z = vec.z * ViewportZScale + ViewportZCenter; +// +// That will give us the final value between 0 and 65535, which we can simply floor to simulate +// the limited precision of the PSP's depth buffer. Then we convert it back: +// z = floor(z); +// +// vec.z = (z - ViewportZCenter) / ViewportZScale; +// +// Now, the regular machinery will take over and do the calculation again. +// +// Depth is not clipped to the viewport, but does clip to "minz" and "maxz". It may also be clamped +// to 0 and 65535 if a depth clamping/clipping flag is set (x/y clipping is performed only if depth +// needs to be clamped.) +// +// All this above is for full transform mode. +// In through mode, the Z coordinate just goes straight through and there is no perspective division. +// We simulate this of course with pretty much an identity matrix. Rounding Z becomes very easy. +// +// TODO: Skip all this if we can actually get a 16-bit depth buffer along with stencil, which +// is a bit of a rare configuration, although quite common on mobile. + +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", +}; + +extern const char *vulkan_glsl_preamble_vs; +extern const char *hlsl_preamble_vs; + +bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguageDesc &compat, uint32_t *attrMask, uint64_t *uniformMask, std::string *errorString) { + *attrMask = 0; + *uniformMask = 0; + + bool highpFog = false; + bool highpTexcoord = false; + + std::vector gl_exts; + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (gl_extensions.EXT_gpu_shader4) { + gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable"); + } + } + ShaderWriter p(buffer, compat, ShaderStage::Vertex, gl_exts.data(), gl_exts.size()); + + bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH); + bool lmode = id.Bit(VS_BIT_LMODE); + bool doTexture = id.Bit(VS_BIT_DO_TEXTURE); + + GETexMapMode uvGenMode = static_cast(id.Bits(VS_BIT_UVGEN_MODE, 2)); + bool doTextureTransform = uvGenMode == GE_TEXMAP_TEXTURE_MATRIX; + + // this is only valid for some settings of uvGenMode + GETexProjMapMode uvProjMode = static_cast(id.Bits(VS_BIT_UVPROJ_MODE, 2)); + bool doShadeMapping = uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP; + bool doFlatShading = id.Bit(VS_BIT_FLATSHADE); + + bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); + bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; + bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform; + bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform; + bool enableFog = id.Bit(VS_BIT_ENABLE_FOG); + 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) && useHWTransform; + bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE); + int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3); + + // Apparently we don't support bezier/spline together with bones. + bool doBezier = id.Bit(VS_BIT_BEZIER) && !enableBones && useHWTransform; + bool doSpline = id.Bit(VS_BIT_SPLINE) && !enableBones && useHWTransform; + if (doBezier || doSpline) { + if (!hasNormal) { + // Bad usage. + *errorString = "Invalid flags - tess requires normal."; + return false; + } + if (compat.shaderLanguage == HLSL_D3D9 || compat.texelFetch == nullptr) { + *errorString = "Tess not supported on this shader language version"; + return false; + } + } + bool hasColorTess = id.Bit(VS_BIT_HAS_COLOR_TESS); + bool hasTexcoordTess = id.Bit(VS_BIT_HAS_TEXCOORD_TESS); + bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS); + bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS); + + const char *shading = ""; + if (compat.glslES30 || compat.shaderLanguage == GLSL_VULKAN) + shading = doFlatShading ? "flat " : ""; + + DoLightComputation doLight[4] = { LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF }; + if (useHWTransform) { + int shadeLight0 = doShadeMapping ? ls0 : -1; + int shadeLight1 = doShadeMapping ? ls1 : -1; + for (int i = 0; i < 4; i++) { + if (i == shadeLight0 || i == shadeLight1) + doLight[i] = LIGHT_SHADE; + if (enableLighting && id.Bit(VS_BIT_LIGHT0_ENABLE + i)) + doLight[i] = LIGHT_FULL; + } + } + + int numBoneWeights = 0; + int boneWeightScale = id.Bits(VS_BIT_WEIGHT_FMTSCALE, 2); + if (enableBones) { + numBoneWeights = 1 + id.Bits(VS_BIT_BONES, 3); + } + bool texCoordInVec3 = false; + + if (compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, "\n"); + WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseVars {\n%s};\n", ub_baseStr); + if (enableLighting || doShadeMapping) + WRITE(p, "layout (std140, set = 0, binding = 4) uniform lightVars {\n%s};\n", ub_vs_lightsStr); + if (enableBones) + WRITE(p, "layout (std140, set = 0, binding = 5) uniform boneVars {\n%s};\n", ub_vs_bonesStr); + + if (enableBones) { + WRITE(p, "%s", boneWeightDecl[numBoneWeights]); + } + + if (useHWTransform) + WRITE(p, "layout (location = %d) in vec3 position;\n", (int)PspAttributeLocation::POSITION); + else + // we pass the fog coord in w + WRITE(p, "layout (location = %d) in vec4 position;\n", (int)PspAttributeLocation::POSITION); + + if (useHWTransform && hasNormal) + WRITE(p, "layout (location = %d) in vec3 normal;\n", (int)PspAttributeLocation::NORMAL); + + if (doTexture && hasTexcoord) { + if (!useHWTransform && doTextureTransform && !isModeThrough) { + WRITE(p, "layout (location = %d) in vec3 texcoord;\n", (int)PspAttributeLocation::TEXCOORD); + texCoordInVec3 = true; + } else + WRITE(p, "layout (location = %d) in vec2 texcoord;\n", (int)PspAttributeLocation::TEXCOORD); + } + if (hasColor) { + WRITE(p, "layout (location = %d) in vec4 color0;\n", (int)PspAttributeLocation::COLOR0); + if (lmode && !useHWTransform) // only software transform supplies color1 as vertex data + WRITE(p, "layout (location = %d) in vec3 color1;\n", (int)PspAttributeLocation::COLOR1); + } + + WRITE(p, "layout (location = 1) %sout lowp vec4 v_color0;\n", shading); + if (lmode) { + WRITE(p, "layout (location = 2) %sout lowp vec3 v_color1;\n", shading); + } + + if (doTexture) { + WRITE(p, "layout (location = 0) out highp vec3 v_texcoord;\n"); + } + + if (enableFog) { + // See the fragment shader generator + WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n"); + } + } else if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) { + // Note: These two share some code after this hellishly large if/else. + if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "cbuffer base : register(b0) {\n%s};\n", ub_baseStr); + WRITE(p, "cbuffer lights: register(b1) {\n%s};\n", ub_vs_lightsStr); + WRITE(p, "cbuffer bones : register(b2) {\n%s};\n", ub_vs_bonesStr); + } else { + WRITE(p, "#pragma warning( disable : 3571 )\n"); + if (isModeThrough) { + WRITE(p, "mat4 u_proj_through : register(c%i);\n", CONST_VS_PROJ_THROUGH); + } else { + WRITE(p, "mat4 u_proj : register(c%i);\n", CONST_VS_PROJ); + // Add all the uniforms we'll need to transform properly. + } + + if (enableFog) { + WRITE(p, "vec2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF); + } + if (useHWTransform || !hasColor) + WRITE(p, "vec4 u_matambientalpha : register(c%i);\n", CONST_VS_MATAMBIENTALPHA); // matambient + matalpha + + if (useHWTransform) { + // When transforming by hardware, we need a great deal more uniforms... + WRITE(p, "mat3x4 u_world : register(c%i);\n", CONST_VS_WORLD); + WRITE(p, "mat3x4 u_view : register(c%i);\n", CONST_VS_VIEW); + if (doTextureTransform) + WRITE(p, "mat3x4 u_texmtx : register(c%i);\n", CONST_VS_TEXMTX); + if (enableBones) { +#ifdef USE_BONE_ARRAY + WRITE(p, "mat3x4 u_bone[%i] : register(c%i);\n", numBones, CONST_VS_BONE0); +#else + for (int i = 0; i < numBoneWeights; i++) { + WRITE(p, "mat3x4 u_bone%i : register(c%i);\n", i, CONST_VS_BONE0 + i * 3); + } +#endif + } + if (doTexture) { + WRITE(p, "vec4 u_uvscaleoffset : register(c%i);\n", CONST_VS_UVSCALEOFFSET); + } + for (int i = 0; i < 4; i++) { + if (doLight[i] != LIGHT_OFF) { + // This is needed for shade mapping + WRITE(p, "vec3 u_lightpos%i : register(c%i);\n", i, CONST_VS_LIGHTPOS + i); + } + if (doLight[i] == LIGHT_FULL) { + GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); + GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); + + if (type != GE_LIGHTTYPE_DIRECTIONAL) + WRITE(p, "vec3 u_lightatt%i : register(c%i);\n", i, CONST_VS_LIGHTATT + i); + + if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { + WRITE(p, "vec3 u_lightdir%i : register(c%i);\n", i, CONST_VS_LIGHTDIR + i); + WRITE(p, "vec4 u_lightangle_spotCoef%i : register(c%i);\n", i, CONST_VS_LIGHTANGLE_SPOTCOEF + i); + } + WRITE(p, "vec3 u_lightambient%i : register(c%i);\n", i, CONST_VS_LIGHTAMBIENT + i); + WRITE(p, "vec3 u_lightdiffuse%i : register(c%i);\n", i, CONST_VS_LIGHTDIFFUSE + i); + + if (comp == GE_LIGHTCOMP_BOTH) { + WRITE(p, "vec3 u_lightspecular%i : register(c%i);\n", i, CONST_VS_LIGHTSPECULAR + i); + } + } + } + if (enableLighting) { + WRITE(p, "vec4 u_ambient : register(c%i);\n", CONST_VS_AMBIENT); + if ((matUpdate & 2) == 0 || !hasColor) + WRITE(p, "vec3 u_matdiffuse : register(c%i);\n", CONST_VS_MATDIFFUSE); + // if ((matUpdate & 4) == 0) + WRITE(p, "vec4 u_matspecular : register(c%i);\n", CONST_VS_MATSPECULAR); // Specular coef is contained in alpha + WRITE(p, "vec3 u_matemissive : register(c%i);\n", CONST_VS_MATEMISSIVE); + } + } + + if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { + WRITE(p, "vec4 u_depthRange : register(c%i);\n", CONST_VS_DEPTHRANGE); + } + if (!isModeThrough) { + WRITE(p, "vec4 u_cullRangeMin : register(c%i);\n", CONST_VS_CULLRANGEMIN); + WRITE(p, "vec4 u_cullRangeMax : register(c%i);\n", CONST_VS_CULLRANGEMAX); + } + } + + // And the "varyings". + if (useHWTransform) { + WRITE(p, "struct VS_IN { \n"); + if ((doSpline || doBezier) && compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, " uint instanceId : SV_InstanceID;\n"); + } + if (enableBones) { + WRITE(p, " %s", boneWeightAttrDeclHLSL[numBoneWeights]); + } + if (doTexture && hasTexcoord) { + WRITE(p, " vec2 texcoord : TEXCOORD0;\n"); + } + if (hasColor) { + WRITE(p, " vec4 color0 : COLOR0;\n"); + } + if (hasNormal) { + WRITE(p, " vec3 normal : NORMAL;\n"); + } + WRITE(p, " vec3 position : POSITION;\n"); + WRITE(p, "};\n"); + } else { + WRITE(p, "struct VS_IN {\n"); + WRITE(p, " vec4 position : POSITION;\n"); + if (doTexture && hasTexcoord) { + if (doTextureTransform && !isModeThrough) { + texCoordInVec3 = true; + WRITE(p, " vec3 texcoord : TEXCOORD0;\n"); + } else + WRITE(p, " vec2 texcoord : TEXCOORD0;\n"); + } + if (hasColor) { + WRITE(p, " vec4 color0 : COLOR0;\n"); + } + // only software transform supplies color1 as vertex data + if (lmode) { + WRITE(p, " vec3 color1 : COLOR1;\n"); + } + WRITE(p, "};\n"); + } + + WRITE(p, "struct VS_OUT {\n"); + if (doTexture) { + WRITE(p, " vec3 v_texcoord : TEXCOORD0;\n"); + } + const char *colorInterpolation = doFlatShading && compat.shaderLanguage == HLSL_D3D11 ? "nointerpolation " : ""; + WRITE(p, " %svec4 v_color0 : COLOR0;\n", colorInterpolation); + if (lmode) + WRITE(p, " vec3 v_color1 : COLOR1;\n"); + + if (enableFog) { + WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); + } + if (compat.shaderLanguage == HLSL_D3D9) { + WRITE(p, " vec4 gl_Position : POSITION;\n"); + } else { + WRITE(p, " vec4 gl_Position : SV_Position;\n"); + } + WRITE(p, "};\n"); + } else { + if (enableBones) { + const char * const * boneWeightDecl = boneWeightAttrDecl; + if (!strcmp(compat.attribute, "in")) { + boneWeightDecl = boneWeightInDecl; + } + WRITE(p, "%s", boneWeightDecl[numBoneWeights]); + *attrMask |= 1 << ATTR_W1; + if (numBoneWeights >= 5) + *attrMask |= 1 << ATTR_W2; + } + + if (useHWTransform) + WRITE(p, "%s vec3 position;\n", compat.attribute); + else + WRITE(p, "%s vec4 position;\n", compat.attribute); // need to pass the fog coord in w + *attrMask |= 1 << ATTR_POSITION; + + if (useHWTransform && hasNormal) { + WRITE(p, "%s mediump vec3 normal;\n", compat.attribute); + *attrMask |= 1 << ATTR_NORMAL; + } + + if (doTexture && hasTexcoord) { + if (!useHWTransform && doTextureTransform && !isModeThrough) { + WRITE(p, "%s vec3 texcoord;\n", compat.attribute); + texCoordInVec3 = true; + } else { + WRITE(p, "%s vec2 texcoord;\n", compat.attribute); + } + *attrMask |= 1 << ATTR_TEXCOORD; + } + if (hasColor) { + WRITE(p, "%s lowp vec4 color0;\n", compat.attribute); + *attrMask |= 1 << ATTR_COLOR0; + if (lmode && !useHWTransform) { // only software transform supplies color1 as vertex data + WRITE(p, "%s lowp vec3 color1;\n", compat.attribute); + *attrMask |= 1 << ATTR_COLOR1; + } + } + + if (isModeThrough) { + WRITE(p, "uniform mat4 u_proj_through;\n"); + *uniformMask |= DIRTY_PROJTHROUGHMATRIX; + } else { + WRITE(p, "uniform mat4 u_proj;\n"); + *uniformMask |= DIRTY_PROJMATRIX; + // Add all the uniforms we'll need to transform properly. + } + + if (useHWTransform) { + // When transforming by hardware, we need a great deal more uniforms... + // TODO: Use 4x3 matrices where possible. Though probably doesn't matter much. + WRITE(p, "uniform mat4 u_world;\n"); + WRITE(p, "uniform mat4 u_view;\n"); + *uniformMask |= DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX; + if (doTextureTransform) { + WRITE(p, "uniform mediump mat4 u_texmtx;\n"); + *uniformMask |= DIRTY_TEXMATRIX; + } + if (enableBones) { + for (int i = 0; i < numBoneWeights; i++) { + WRITE(p, "uniform mat4 u_bone%i;\n", i); + *uniformMask |= DIRTY_BONEMATRIX0 << i; + } + } + if (doTexture) { + WRITE(p, "uniform vec4 u_uvscaleoffset;\n"); + *uniformMask |= DIRTY_UVSCALEOFFSET; + } + for (int i = 0; i < 4; i++) { + if (doLight[i] != LIGHT_OFF) { + // This is needed for shade mapping + WRITE(p, "uniform vec3 u_lightpos%i;\n", i); + *uniformMask |= DIRTY_LIGHT0 << i; + } + if (doLight[i] == LIGHT_FULL) { + *uniformMask |= DIRTY_LIGHT0 << i; + GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); + GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); + + if (type != GE_LIGHTTYPE_DIRECTIONAL) + WRITE(p, "uniform mediump vec3 u_lightatt%i;\n", i); + + if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { + WRITE(p, "uniform mediump vec3 u_lightdir%i;\n", i); + WRITE(p, "uniform mediump vec2 u_lightangle_spotCoef%i;\n", i); + } + WRITE(p, "uniform lowp vec3 u_lightambient%i;\n", i); + WRITE(p, "uniform lowp vec3 u_lightdiffuse%i;\n", i); + + if (comp == GE_LIGHTCOMP_BOTH) { + WRITE(p, "uniform lowp vec3 u_lightspecular%i;\n", i); + } + } + } + if (enableLighting) { + WRITE(p, "uniform lowp vec4 u_ambient;\n"); + *uniformMask |= DIRTY_AMBIENT; + if ((matUpdate & 2) == 0 || !hasColor) { + WRITE(p, "uniform lowp vec3 u_matdiffuse;\n"); + *uniformMask |= DIRTY_MATDIFFUSE; + } + WRITE(p, "uniform lowp vec4 u_matspecular;\n"); // Specular coef is contained in alpha + WRITE(p, "uniform lowp vec3 u_matemissive;\n"); + *uniformMask |= DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE; + } + } + + if (useHWTransform || !hasColor) { + WRITE(p, "uniform lowp vec4 u_matambientalpha;\n"); // matambient + matalpha + *uniformMask |= DIRTY_MATAMBIENTALPHA; + } + if (enableFog) { + WRITE(p, "uniform highp vec2 u_fogcoef;\n"); + *uniformMask |= DIRTY_FOGCOEF; + } + + if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { + WRITE(p, "uniform highp vec4 u_depthRange;\n"); + *uniformMask |= DIRTY_DEPTHRANGE; + } + + if (!isModeThrough) { + WRITE(p, "uniform highp vec4 u_cullRangeMin;\n"); + WRITE(p, "uniform highp vec4 u_cullRangeMax;\n"); + *uniformMask |= DIRTY_CULLRANGE; + } + + WRITE(p, "%s%s lowp vec4 v_color0;\n", shading, compat.varying_vs); + if (lmode) { + WRITE(p, "%s%s lowp vec3 v_color1;\n", shading, compat.varying_vs); + } + + if (doTexture) { + WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_vs, highpTexcoord ? "highp" : "mediump"); + } + + if (enableFog) { + // See the fragment shader generator + if (highpFog) { + WRITE(p, "%s highp float v_fogdepth;\n", compat.varying_vs); + } else { + WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs); + } + } + } + + // See comment above this function (GenerateVertexShader). + if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { + // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. + WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n"); + WRITE(p, " float z = v.z / v.w;\n"); + WRITE(p, " z = z * u_depthRange.x + u_depthRange.y;\n"); + WRITE(p, " z = floor(z);\n"); + WRITE(p, " z = (z - u_depthRange.z) * u_depthRange.w;\n"); + WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n"); + WRITE(p, "}\n\n"); + } + + // Hardware tessellation + if (doBezier || doSpline) { + *uniformMask |= DIRTY_BEZIERSPLINE; + + if (compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, "struct TessData {\n"); + WRITE(p, " vec4 pos;\n"); + WRITE(p, " vec4 tex;\n"); + WRITE(p, " vec4 col;\n"); + WRITE(p, "};\n"); + WRITE(p, "layout (std430, set = 0, binding = 6) readonly buffer s_tess_data {\n"); + WRITE(p, " TessData tess_data[];\n"); + WRITE(p, "};\n"); + + WRITE(p, "struct TessWeight {\n"); + WRITE(p, " vec4 basis;\n"); + WRITE(p, " vec4 deriv;\n"); + WRITE(p, "};\n"); + WRITE(p, "layout (std430, set = 0, binding = 7) readonly buffer s_tess_weights_u {\n"); + WRITE(p, " TessWeight tess_weights_u[];\n"); + WRITE(p, "};\n"); + WRITE(p, "layout (std430, set = 0, binding = 8) readonly buffer s_tess_weights_v {\n"); + WRITE(p, " TessWeight tess_weights_v[];\n"); + WRITE(p, "};\n"); + } else if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + WRITE(p, "uniform sampler2D u_tess_points;\n"); // Control Points + WRITE(p, "uniform sampler2D u_tess_weights_u;\n"); + WRITE(p, "uniform sampler2D u_tess_weights_v;\n"); + + WRITE(p, "uniform int u_spline_counts;\n"); + } else if (compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "struct TessData {\n"); + WRITE(p, " vec3 pos; float pad1;\n"); + WRITE(p, " vec2 tex; vec2 pad2;\n"); + WRITE(p, " vec4 col;\n"); + WRITE(p, "};\n"); + WRITE(p, "StructuredBuffer tess_data : register(t0);\n"); + + WRITE(p, "struct TessWeight {\n"); + WRITE(p, " vec4 basis;\n"); + WRITE(p, " vec4 deriv;\n"); + WRITE(p, "};\n"); + WRITE(p, "StructuredBuffer tess_weights_u : register(t1);\n"); + WRITE(p, "StructuredBuffer tess_weights_v : register(t2);\n"); + } else if (compat.shaderLanguage == HLSL_D3D9) { + + } + + const char *init[3] = { "0.0, 0.0", "0.0, 0.0, 0.0", "0.0, 0.0, 0.0, 0.0" }; + for (int i = 2; i <= 4; i++) { + // Define 3 types vec2, vec3, vec4 + WRITE(p, "vec%d tess_sample(in vec%d points[16], mat4 weights) {\n", i, i); + WRITE(p, " vec%d pos = vec%d(%s);\n", i, i, init[i - 2]); + for (int v = 0; v < 4; ++v) { + for (int u = 0; u < 4; ++u) { + WRITE(p, " pos += weights[%i][%i] * points[%i];\n", v, u, v * 4 + u); + } + } + WRITE(p, " return pos;\n"); + WRITE(p, "}\n"); + } + + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) && compat.glslVersionNumber < 130) { // For glsl version 1.10 + WRITE(p, "mat4 outerProduct(vec4 u, vec4 v) {\n"); + WRITE(p, " return mat4(u * v[0], u * v[1], u * v[2], u * v[3]);\n"); + WRITE(p, "}\n"); + } else if (compat.shaderLanguage == HLSL_D3D9 || compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "mat4 outerProduct(vec4 u, vec4 v) {\n"); + WRITE(p, " return mul((float4x1)v, (float1x4)u);\n"); + WRITE(p, "}\n"); + } + + WRITE(p, "struct Tess {\n"); + WRITE(p, " vec3 pos;\n"); + if (doTexture) + WRITE(p, " vec2 tex;\n"); + WRITE(p, " vec4 col;\n"); + if (hasNormalTess) + WRITE(p, " vec3 nrm;\n"); + WRITE(p, "};\n"); + + if (compat.shaderLanguage == HLSL_D3D9 || compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "void tessellate(in VS_IN In, out Tess tess) {\n"); + WRITE(p, " vec3 position = In.position;\n"); + WRITE(p, " vec3 normal = In.normal;\n"); + } else { + WRITE(p, "void tessellate(out Tess tess) {\n"); + } + WRITE(p, " ivec2 point_pos = ivec2(position.z, normal.z)%s;\n", doBezier ? " * 3" : ""); + WRITE(p, " ivec2 weight_idx = ivec2(position.xy);\n"); + + // Load 4x4 control points + WRITE(p, " vec3 _pos[16];\n"); + WRITE(p, " vec2 _tex[16];\n"); + WRITE(p, " vec4 _col[16];\n"); + if (compat.coefsFromBuffers) { + WRITE(p, " int index;\n"); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + WRITE(p, " index = (%i + point_pos.y) * int(u_spline_counts) + (%i + point_pos.x);\n", i, j); + WRITE(p, " _pos[%i] = tess_data[index].pos.xyz;\n", i * 4 + j); + if (doTexture && hasTexcoordTess) + WRITE(p, " _tex[%i] = tess_data[index].tex.xy;\n", i * 4 + j); + if (hasColorTess) + WRITE(p, " _col[%i] = tess_data[index].col;\n", i * 4 + j); + } + } + + // Basis polynomials as weight coefficients + WRITE(p, " vec4 basis_u = tess_weights_u[weight_idx.x].basis;\n"); + WRITE(p, " vec4 basis_v = tess_weights_v[weight_idx.y].basis;\n"); + WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n"); + + } else { + WRITE(p, " int index_u, index_v;\n"); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + WRITE(p, " index_u = (%i + point_pos.x);\n", j); + WRITE(p, " index_v = (%i + point_pos.y);\n", i); + WRITE(p, " _pos[%i] = %s(u_tess_points, ivec2(index_u, index_v), 0).xyz;\n", i * 4 + j, compat.texelFetch); + if (doTexture && hasTexcoordTess) + WRITE(p, " _tex[%i] = %s(u_tess_points, ivec2(index_u + u_spline_counts, index_v), 0).xy;\n", i * 4 + j, compat.texelFetch); + if (hasColorTess) + WRITE(p, " _col[%i] = %s(u_tess_points, ivec2(index_u + u_spline_counts * 2, index_v), 0).rgba;\n", i * 4 + j, compat.texelFetch); + } + } + + // Basis polynomials as weight coefficients + WRITE(p, " vec4 basis_u = %s(u_tess_weights_u, %s, 0);\n", compat.texelFetch, "ivec2(weight_idx.x * 2, 0)"); + WRITE(p, " vec4 basis_v = %s(u_tess_weights_v, %s, 0);\n", compat.texelFetch, "ivec2(weight_idx.y * 2, 0)"); + WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n"); + } + + // Tessellate + WRITE(p, " tess.pos = tess_sample(_pos, basis);\n"); + if (doTexture) { + if (hasTexcoordTess) + WRITE(p, " tess.tex = tess_sample(_tex, basis);\n"); + else + WRITE(p, " tess.tex = normal.xy;\n"); + } + if (hasColorTess) + WRITE(p, " tess.col = tess_sample(_col, basis);\n"); + else + WRITE(p, " tess.col = u_matambientalpha;\n"); + if (hasNormalTess) { + if (compat.coefsFromBuffers) { + // Derivatives as weight coefficients + WRITE(p, " vec4 deriv_u = tess_weights_u[weight_idx.x].deriv;\n"); + WRITE(p, " vec4 deriv_v = tess_weights_v[weight_idx.y].deriv;\n"); + } else { + // Derivatives as weight coefficients + WRITE(p, " vec4 deriv_u = %s(u_tess_weights_u, %s, 0);\n", compat.texelFetch, "ivec2(weight_idx.x * 2 + 1, 0)"); + WRITE(p, " vec4 deriv_v = %s(u_tess_weights_v, %s, 0);\n", compat.texelFetch, "ivec2(weight_idx.y * 2 + 1, 0)"); + } + + WRITE(p, " vec3 du = tess_sample(_pos, outerProduct(deriv_u, basis_v));\n"); + WRITE(p, " vec3 dv = tess_sample(_pos, outerProduct(basis_u, deriv_v));\n"); + WRITE(p, " tess.nrm = normalize(cross(du, dv));\n"); + } + WRITE(p, "}\n"); + } + + if (ShaderLanguageIsOpenGL(compat.shaderLanguage) || compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, "void main() {\n"); + } else if (compat.shaderLanguage == HLSL_D3D9 || compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, "VS_OUT main(VS_IN In) {\n"); + WRITE(p, " VS_OUT Out;\n"); + if (doTexture && hasTexcoord) { + if (texCoordInVec3) { + WRITE(p, " vec3 texcoord = In.texcoord;\n"); + } else { + WRITE(p, " vec2 texcoord = In.texcoord;\n"); + } + } + if (hasColor) { + WRITE(p, " vec4 color0 = In.color0;\n"); + if (lmode && !useHWTransform) { + WRITE(p, " vec3 color1 = In.color1;\n"); + } + } + if (hasNormal) { + WRITE(p, " vec3 normal = In.normal;\n"); + } + if (useHWTransform) { + WRITE(p, " vec3 position = In.position;\n"); + } else { + WRITE(p, " vec4 position = In.position;\n"); + } + if (enableBones) { + WRITE(p, "%s", boneWeightAttrInitHLSL[numBoneWeights]); + } + } + + if (!useHWTransform) { + // Simple pass-through of vertex data to fragment shader + if (doTexture) { + if (texCoordInVec3) { + WRITE(p, " %sv_texcoord = texcoord;\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_texcoord = vec3(texcoord, 1.0);\n", compat.vsOutPrefix); + } + } + if (hasColor) { + WRITE(p, " %sv_color0 = color0;\n", compat.vsOutPrefix); + if (lmode) + WRITE(p, " %sv_color1 = color1;\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_color0 = u_matambientalpha;\n", compat.vsOutPrefix); + if (lmode) + WRITE(p, " %sv_color1 = splat3(0.0);\n", compat.vsOutPrefix); + } + if (enableFog) { + WRITE(p, " %sv_fogdepth = position.w;\n", compat.vsOutPrefix); + } + if (isModeThrough) { + WRITE(p, " vec4 outPos = mul(u_proj_through, vec4(position.xyz, 1.0));\n"); + } else { + // The viewport is used in this case, so need to compensate for that. + if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { + WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj, vec4(position.xyz, 1.0)));\n"); + } else { + WRITE(p, " vec4 outPos = mul(u_proj, vec4(position.xyz, 1.0));\n"); + } + } + } else { + // Step 1: World Transform / Skinning + if (!enableBones) { + if (doBezier || doSpline) { + // Hardware tessellation + WRITE(p, " Tess tess;\n"); + if (compat.shaderLanguage == HLSL_D3D9 || compat.shaderLanguage == HLSL_D3D11) { + WRITE(p, " tessellate(In, tess);\n"); + } else { + WRITE(p, " tessellate(tess);\n"); + } + + WRITE(p, " vec3 worldpos = mul(vec4(tess.pos.xyz, 1.0), u_world).xyz;\n"); + if (hasNormalTess) { + WRITE(p, " mediump vec3 worldnormal = normalize(mul(vec4(%stess.nrm, 0.0), u_world).xyz);\n", flipNormalTess ? "-" : ""); + } else { + WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n"); + } + } else { + // No skinning, just standard T&L. + WRITE(p, " vec3 worldpos = mul(vec4(position, 1.0), u_world).xyz;\n"); + if (hasNormal) + WRITE(p, " mediump vec3 worldnormal = normalize(mul(vec4(%snormal, 0.0), u_world).xyz);\n", flipNormal ? "-" : ""); + else + WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n"); + } + } else { + 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", + }; + + const char *boneMatrix = compat.forceMatrix4x4 ? "mat4" : "mat3x4"; + + // Uncomment this to screw up bone shaders to check the vertex shader software fallback + // WRITE(p, "THIS SHOULD ERROR! #error"); + if (numBoneWeights == 1 && ShaderLanguageIsOpenGL(compat.shaderLanguage)) + WRITE(p, " %s skinMatrix = w1 * u_bone0", boneMatrix); + else + WRITE(p, " %s skinMatrix = w1.x * u_bone0", boneMatrix); + for (int i = 1; i < numBoneWeights; i++) { + const char *weightAttr = boneWeightAttr[i]; + // workaround for "cant do .x of scalar" issue. + if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) { + if (numBoneWeights == 1 && i == 0) weightAttr = "w1"; + if (numBoneWeights == 5 && i == 4) weightAttr = "w2"; + } + WRITE(p, " + %s * u_bone%i", weightAttr, i); + } + + WRITE(p, ";\n"); + + WRITE(p, " vec3 skinnedpos = mul(vec4(position, 1.0), skinMatrix).xyz%s;\n", factor); + WRITE(p, " vec3 worldpos = mul(vec4(skinnedpos, 1.0), u_world).xyz;\n"); + + if (hasNormal) { + WRITE(p, " mediump vec3 skinnednormal = mul(vec4(%snormal, 0.0), skinMatrix).xyz%s;\n", flipNormal ? "-" : "", factor); + } else { + WRITE(p, " mediump vec3 skinnednormal = mul(vec4(0.0, 0.0, %s1.0, 0.0), skinMatrix).xyz%s;\n", flipNormal ? "-" : "", factor); + } + WRITE(p, " mediump vec3 worldnormal = normalize(mul(vec4(skinnednormal, 0.0), u_world).xyz);\n"); + } + + WRITE(p, " vec4 viewPos = vec4(mul(vec4(worldpos, 1.0), u_view).xyz, 1.0);\n"); + + // Final view and projection transforms. + if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { + WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj, viewPos));\n"); + } else { + WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n"); + } + + // TODO: Declare variables for dots for shade mapping if needed. + + const char *ambientStr = (matUpdate & 1) && hasColor ? "color0" : "u_matambientalpha"; + const char *diffuseStr = (matUpdate & 2) && hasColor ? "color0.rgb" : "u_matdiffuse"; + const char *specularStr = (matUpdate & 4) && hasColor ? "color0.rgb" : "u_matspecular.rgb"; + if (doBezier || doSpline) { + // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. + ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "u_matambientalpha"; + diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "u_matdiffuse"; + specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "u_matspecular.rgb"; + } + + bool diffuseIsZero = true; + bool specularIsZero = true; + bool distanceNeeded = false; + bool anySpots = false; + if (enableLighting) { + WRITE(p, " lowp vec4 lightSum0 = u_ambient * %s + vec4(u_matemissive, 0.0);\n", ambientStr); + + for (int i = 0; i < 4; i++) { + GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); + GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2)); + if (doLight[i] != LIGHT_FULL) + continue; + diffuseIsZero = false; + if (comp == GE_LIGHTCOMP_BOTH) + specularIsZero = false; + if (type != GE_LIGHTTYPE_DIRECTIONAL) + distanceNeeded = true; + if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) + anySpots = true; + } + + if (!specularIsZero) { + WRITE(p, " lowp vec3 lightSum1 = splat3(0.0);\n"); + } + if (!diffuseIsZero) { + WRITE(p, " vec3 toLight;\n"); + WRITE(p, " lowp vec3 diffuse;\n"); + } + if (distanceNeeded) { + WRITE(p, " float distance;\n"); + WRITE(p, " lowp float lightScale;\n"); + } + WRITE(p, " mediump float ldot;\n"); + if (anySpots) { + WRITE(p, " lowp float angle;\n"); + } + } + + // Calculate lights if needed. If shade mapping is enabled, lights may need to be + // at least partially calculated. + for (int i = 0; i < 4; i++) { + if (doLight[i] != LIGHT_FULL) + continue; + + GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); + GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2)); + + if (type == GE_LIGHTTYPE_DIRECTIONAL) { + // We prenormalize light positions for directional lights. + WRITE(p, " toLight = u_lightpos%i;\n", i); + } else { + WRITE(p, " toLight = u_lightpos%i - worldpos;\n", i); + WRITE(p, " distance = length(toLight);\n"); + WRITE(p, " toLight /= distance;\n"); + } + + bool doSpecular = comp == GE_LIGHTCOMP_BOTH; + bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; + + WRITE(p, " ldot = dot(toLight, worldnormal);\n"); + if (poweredDiffuse) { + // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. + // Seen in Tales of the World: Radiant Mythology (#2424.) + WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); + WRITE(p, " ldot = 1.0;\n"); + WRITE(p, " } else {\n"); + WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + WRITE(p, " }\n"); + } + + const char *timesLightScale = " * lightScale"; + + // Attenuation + switch (type) { + case GE_LIGHTTYPE_DIRECTIONAL: + timesLightScale = ""; + break; + case GE_LIGHTTYPE_POINT: + WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); + break; + case GE_LIGHTTYPE_SPOT: + case GE_LIGHTTYPE_UNKNOWN: + WRITE(p, " angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); + WRITE(p, " if (angle >= u_lightangle_spotCoef%i.x) {\n", i); + WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); + WRITE(p, " } else {\n"); + WRITE(p, " lightScale = 0.0;\n"); + WRITE(p, " }\n"); + break; + default: + // ILLEGAL + break; + } + + WRITE(p, " diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); + if (doSpecular) { + WRITE(p, " if (ldot >= 0.0) {\n"); + WRITE(p, " ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); + WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); + WRITE(p, " ldot = 1.0;\n"); + WRITE(p, " } else {\n"); + WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); + WRITE(p, " }\n"); + WRITE(p, " if (ldot > 0.0)\n"); + WRITE(p, " lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); + WRITE(p, " }\n"); + } + WRITE(p, " lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); + } + + if (enableLighting) { + // Sum up ambient, emissive here. + if (lmode) { + WRITE(p, " %sv_color0 = clamp(lightSum0, 0.0, 1.0);\n", compat.vsOutPrefix); + // v_color1 only exists when lmode = 1. + if (specularIsZero) { + WRITE(p, " %sv_color1 = splat3(0.0);\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_color1 = clamp(lightSum1, 0.0, 1.0);\n", compat.vsOutPrefix); + } + } else { + if (specularIsZero) { + WRITE(p, " %sv_color0 = clamp(lightSum0, 0.0, 1.0);\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_color0 = clamp(clamp(lightSum0, 0.0, 1.0) + vec4(lightSum1, 0.0), 0.0, 1.0);\n", compat.vsOutPrefix); + } + } + } else { + // Lighting doesn't affect color. + if (hasColor) { + if (doBezier || doSpline) + WRITE(p, " %sv_color0 = tess.col;\n", compat.vsOutPrefix); + else + WRITE(p, " %sv_color0 = color0;\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_color0 = u_matambientalpha;\n", compat.vsOutPrefix); + } + if (lmode) + WRITE(p, " %sv_color1 = splat3(0.0);\n", compat.vsOutPrefix); + } + + bool scaleUV = !isModeThrough && (uvGenMode == GE_TEXMAP_TEXTURE_COORDS || uvGenMode == GE_TEXMAP_UNKNOWN); + + // Step 3: UV generation + if (doTexture) { + switch (uvGenMode) { + case GE_TEXMAP_TEXTURE_COORDS: // Scale-offset. Easy. + case GE_TEXMAP_UNKNOWN: // Not sure what this is, but Riviera uses it. Treating as coords works. + if (scaleUV) { + if (hasTexcoord) { + if (doBezier || doSpline) + WRITE(p, " %sv_texcoord = vec3(tess.tex.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); + else + WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy, 0.0);\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_texcoord = splat3(0.0);\n", compat.vsOutPrefix); + } + } else { + if (hasTexcoord) { + WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); + } else { + WRITE(p, " %sv_texcoord = vec3(u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix); + } + } + break; + + case GE_TEXMAP_TEXTURE_MATRIX: // Projection mapping. + { + std::string temp_tc; + switch (uvProjMode) { + case GE_PROJMAP_POSITION: // Use model space XYZ as source + temp_tc = "vec4(position, 1.0)"; + break; + case GE_PROJMAP_UV: // Use unscaled UV as source + { + // prescale is false here. + if (hasTexcoord) { + temp_tc = "vec4(texcoord.xy, 0.0, 1.0)"; + } else { + temp_tc = "vec4(0.0, 0.0, 0.0, 1.0)"; + } + } + break; + case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source + if (hasNormal) + temp_tc = flipNormal ? "vec4(normalize(-normal), 1.0)" : "vec4(normalize(normal), 1.0)"; + else + temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; + break; + case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source + if (hasNormal) + temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)"; + else + temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; + break; + } + // Transform by texture matrix. XYZ as we are doing projection mapping. + WRITE(p, " %sv_texcoord = mul(%s, u_texmtx).xyz * vec3(u_uvscaleoffset.xy, 1.0);\n", compat.vsOutPrefix, temp_tc.c_str()); + } + break; + + case GE_TEXMAP_ENVIRONMENT_MAP: // Shade mapping - use dots from light sources. + { + std::string lightFactor0 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls0, ls0); + std::string lightFactor1 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls1, ls1); + WRITE(p, " %sv_texcoord = vec3(u_uvscaleoffset.xy * vec2(1.0 + %s, 1.0 + %s) * 0.5, 1.0);\n", compat.vsOutPrefix, lightFactor0.c_str(), lightFactor1.c_str()); + } + break; + + default: + // ILLEGAL + break; + } + } + + // Compute fogdepth + if (enableFog) + WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix); + } + + if (!isModeThrough && gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING)) { + WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); + // Vertex range culling doesn't happen when depth is clamped, so only do this if in range. + WRITE(p, " if (u_cullRangeMin.w <= 0.0 || (projPos.z >= u_cullRangeMin.z && projPos.z <= u_cullRangeMax.z)) {\n"); + const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y || projPos.z < u_cullRangeMin.z"; + const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y || projPos.z > u_cullRangeMax.z"; + WRITE(p, " if (%s || %s) {\n", outMin, outMax); + WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n"); + WRITE(p, " }\n"); + WRITE(p, " }\n"); + } + + // We've named the output gl_Position in HLSL as well. + WRITE(p, " %sgl_Position = outPos;\n", compat.vsOutPrefix); + if (compat.shaderLanguage == GLSL_VULKAN) { + WRITE(p, " gl_PointSize = 1.0;\n"); + } + if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) { + WRITE(p, " return Out;\n"); + } + WRITE(p, "}\n"); + return true; +} diff --git a/GPU/Common/VertexShaderGenerator.h b/GPU/Common/VertexShaderGenerator.h new file mode 100644 index 0000000000..df62f76289 --- /dev/null +++ b/GPU/Common/VertexShaderGenerator.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012- 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/. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "Common/GPU/Shader.h" + +struct VShaderID; + +bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguageDesc &compat, uint32_t *attrMask, uint64_t *uniformMask, std::string *errorString); + +// D3D9 constants. +enum { + CONST_VS_PROJ = 0, + CONST_VS_PROJ_THROUGH = 4, + CONST_VS_VIEW = 8, + CONST_VS_WORLD = 11, + CONST_VS_TEXMTX = 14, + CONST_VS_UVSCALEOFFSET = 17, + CONST_VS_FOGCOEF = 18, + CONST_VS_AMBIENT = 19, + CONST_VS_MATAMBIENTALPHA = 20, + CONST_VS_MATDIFFUSE = 21, + CONST_VS_MATSPECULAR = 22, + CONST_VS_MATEMISSIVE = 23, + CONST_VS_LIGHTPOS = 24, + CONST_VS_LIGHTDIR = 28, + CONST_VS_LIGHTATT = 32, + CONST_VS_LIGHTANGLE_SPOTCOEF = 36, + CONST_VS_LIGHTDIFFUSE = 40, + 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, + CONST_VS_CULLRANGEMIN = 80, + CONST_VS_CULLRANGEMAX = 81, +}; diff --git a/GPU/D3D11/D3D11Util.cpp b/GPU/D3D11/D3D11Util.cpp index ee6ad435d3..d8a68c5f14 100644 --- a/GPU/D3D11/D3D11Util.cpp +++ b/GPU/D3D11/D3D11Util.cpp @@ -19,20 +19,27 @@ #include "D3D11Util.h" -static std::vector CompileShaderToBytecode(const char *code, size_t codeSize, const char *target, UINT flags) { +std::vector CompileShaderToBytecodeD3D11(const char *code, size_t codeSize, const char *target, UINT flags) { ID3DBlob *compiledCode = nullptr; ID3DBlob *errorMsgs = nullptr; HRESULT result = ptr_D3DCompile(code, codeSize, nullptr, nullptr, nullptr, "main", target, flags, 0, &compiledCode, &errorMsgs); std::string errors; if (errorMsgs) { errors = std::string((const char *)errorMsgs->GetBufferPointer(), errorMsgs->GetBufferSize()); - ERROR_LOG(G3D, "%s: %s", SUCCEEDED(result) ? "warnings" : "errors", errors.c_str()); + if (SUCCEEDED(result)) { + WARN_LOG(G3D, "%s: %s", "warnings", errors.c_str()); + } else { + ERROR_LOG(G3D, "%s: %s", "errors", errors.c_str()); + } OutputDebugStringA(LineNumberString(code).c_str()); + OutputDebugStringA(errors.c_str()); errorMsgs->Release(); } if (compiledCode) { + // Success! const uint8_t *buf = (const uint8_t *)compiledCode->GetBufferPointer(); std::vector compiled = std::vector(buf, buf + compiledCode->GetBufferSize()); + _assert_(compiled.size() != 0); compiledCode->Release(); return compiled; } @@ -41,7 +48,7 @@ static std::vector CompileShaderToBytecode(const char *code, size_t cod ID3D11VertexShader *CreateVertexShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, std::vector *byteCodeOut, D3D_FEATURE_LEVEL featureLevel, UINT flags) { const char *profile = featureLevel <= D3D_FEATURE_LEVEL_9_3 ? "vs_4_0_level_9_1" : "vs_4_0"; - std::vector byteCode = CompileShaderToBytecode(code, codeSize, profile, flags); + std::vector byteCode = CompileShaderToBytecodeD3D11(code, codeSize, profile, flags); if (byteCode.empty()) return nullptr; @@ -54,7 +61,7 @@ ID3D11VertexShader *CreateVertexShaderD3D11(ID3D11Device *device, const char *co ID3D11PixelShader *CreatePixelShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, D3D_FEATURE_LEVEL featureLevel, UINT flags) { const char *profile = featureLevel <= D3D_FEATURE_LEVEL_9_3 ? "ps_4_0_level_9_1" : "ps_4_0"; - std::vector byteCode = CompileShaderToBytecode(code, codeSize, profile, flags); + std::vector byteCode = CompileShaderToBytecodeD3D11(code, codeSize, profile, flags); if (byteCode.empty()) return nullptr; @@ -66,7 +73,7 @@ ID3D11PixelShader *CreatePixelShaderD3D11(ID3D11Device *device, const char *code ID3D11ComputeShader *CreateComputeShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, D3D_FEATURE_LEVEL featureLevel, UINT flags) { if (featureLevel <= D3D_FEATURE_LEVEL_9_3) return nullptr; - std::vector byteCode = CompileShaderToBytecode(code, codeSize, "cs_4_0", flags); + std::vector byteCode = CompileShaderToBytecodeD3D11(code, codeSize, "cs_4_0", flags); if (byteCode.empty()) return nullptr; @@ -78,7 +85,7 @@ ID3D11ComputeShader *CreateComputeShaderD3D11(ID3D11Device *device, const char * ID3D11GeometryShader *CreateGeometryShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, D3D_FEATURE_LEVEL featureLevel, UINT flags) { if (featureLevel <= D3D_FEATURE_LEVEL_9_3) return nullptr; - std::vector byteCode = CompileShaderToBytecode(code, codeSize, "gs_5_0", flags); + std::vector byteCode = CompileShaderToBytecodeD3D11(code, codeSize, "gs_5_0", flags); if (byteCode.empty()) return nullptr; diff --git a/GPU/D3D11/D3D11Util.h b/GPU/D3D11/D3D11Util.h index 11dba6ee4c..e9ae76ad42 100644 --- a/GPU/D3D11/D3D11Util.h +++ b/GPU/D3D11/D3D11Util.h @@ -18,6 +18,8 @@ #pragma once #include +#include + #include class PushBufferD3D11 { @@ -71,6 +73,8 @@ private: bool nextMapDiscard_ = false; }; +std::vector CompileShaderToBytecodeD3D11(const char *code, size_t codeSize, const char *target, UINT flags); + ID3D11VertexShader *CreateVertexShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, std::vector *byteCodeOut, D3D_FEATURE_LEVEL featureLevel, UINT flags = 0); ID3D11PixelShader *CreatePixelShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, D3D_FEATURE_LEVEL featureLevel, UINT flags = 0); ID3D11ComputeShader *CreateComputeShaderD3D11(ID3D11Device *device, const char *code, size_t codeSize, D3D_FEATURE_LEVEL featureLevel, UINT flags = 0); @@ -95,4 +99,4 @@ public: if (!SUCCEEDED((x))) \ Crash(); -extern StockObjectsD3D11 stockD3D11; \ No newline at end of file +extern StockObjectsD3D11 stockD3D11; diff --git a/GPU/D3D11/DrawEngineD3D11.h b/GPU/D3D11/DrawEngineD3D11.h index fbc94f2d7a..cbca41427a 100644 --- a/GPU/D3D11/DrawEngineD3D11.h +++ b/GPU/D3D11/DrawEngineD3D11.h @@ -28,7 +28,6 @@ #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/GPUStateUtils.h" -#include "GPU/D3D11/FragmentShaderGeneratorD3D11.h" #include "GPU/D3D11/StateMappingD3D11.h" #include "GPU/D3D11/D3D11Util.h" @@ -161,7 +160,7 @@ private: void ApplyDrawState(int prim); void ApplyDrawStateLate(bool applyStencilRef, uint8_t stencilRef); - void ResetShaderBlending(); + void ResetFramebufferRead(); ID3D11InputLayout *SetupDecFmtForDraw(D3D11VertexShader *vshader, const DecVtxFormat &decFmt, u32 pspFmt); diff --git a/GPU/D3D11/FragmentShaderGeneratorD3D11.cpp b/GPU/D3D11/FragmentShaderGeneratorD3D11.cpp deleted file mode 100644 index 07d37c8d5c..0000000000 --- a/GPU/D3D11/FragmentShaderGeneratorD3D11.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2017- 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/. - -#include "GPU/Common/ShaderCommon.h" -#include "GPU/D3D11/FragmentShaderGeneratorD3D11.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" - -void GenerateFragmentShaderD3D11(const FShaderID &id, char *buffer, ShaderLanguage lang) { - DX9::GenerateFragmentShaderHLSL(id, buffer, lang); -} diff --git a/GPU/D3D11/FragmentShaderGeneratorD3D11.h b/GPU/D3D11/FragmentShaderGeneratorD3D11.h deleted file mode 100644 index cf94a63200..0000000000 --- a/GPU/D3D11/FragmentShaderGeneratorD3D11.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2017- 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/. - -#pragma once - -#include "GPU/Common/ShaderId.h" - -void GenerateFragmentShaderD3D11(const FShaderID &id, char *buffer, ShaderLanguage lang); diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 437fe4922c..71234a69c9 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -36,7 +36,7 @@ #include "GPU/Debugger/Stepping.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/PresentationCommon.h" -#include "GPU/Common/ShaderTranslation.h" +#include "Common/GPU/ShaderTranslation.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/D3D11/FramebufferManagerD3D11.h" #include "GPU/D3D11/ShaderManagerD3D11.h" @@ -51,7 +51,7 @@ static const char *vscode = "struct VS_IN {\n" " float4 ObjPos : POSITION;\n" " float2 Uv : TEXCOORD0;\n" - "};" + "};\n" "struct VS_OUT {\n" " float2 Uv : TEXCOORD0;\n" " float4 ProjPos : SV_Position;\n" @@ -252,43 +252,6 @@ void FramebufferManagerD3D11::Bind2DShader() { context_->VSSetShader(quadVertexShader_, 0, 0); } -void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { - if (!useBufferedRendering_ || !vfb->fbo) { - return; - } - - // Technically, we should at this point re-interpret the bytes of the old format to the new. - // That might get tricky, and could cause unnecessary slowness in some games. - // For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts. - // (it uses 565 to write zeros to the buffer, then 4444 to actually render the shadow.) - // - // The best way to do this may ultimately be to create a new FBO (combine with any resize?) - // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex - // to exactly reproduce in 4444 and 8888 formats. - if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "ReformatFramebuffer"); - - context_->OMSetDepthStencilState(stockD3D11.depthStencilDisabled, 0xFF); - context_->OMSetBlendState(stockD3D11.blendStateDisabledWithColorMask[D3D11_COLOR_WRITE_ENABLE_ALPHA], nullptr, 0xFFFFFFFF); - context_->RSSetState(stockD3D11.rasterStateNoCull); - context_->IASetInputLayout(quadInputLayout_); - context_->PSSetShader(quadPixelShader_, nullptr, 0); - context_->VSSetShader(quadVertexShader_, nullptr, 0); - context_->IASetVertexBuffers(0, 1, &fsQuadBuffer_, &quadStride_, &quadOffset_); - context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DClamp); - context_->PSSetShaderResources(0, 1, &nullTextureView_); - shaderManagerD3D11_->DirtyLastShader(); - D3D11_VIEWPORT vp{ 0.0f, 0.0f, (float)vfb->renderWidth, (float)vfb->renderHeight, 0.0f, 1.0f }; - context_->RSSetViewports(1, &vp); - context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - context_->Draw(4, 0); - - textureCache_->ForgetLastTexture(); - - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE); - } -} - static void CopyPixelDepthOnly(u32 *dstp, const u32 *srcp, size_t c) { size_t x = 0; @@ -314,45 +277,6 @@ static void CopyPixelDepthOnly(u32 *dstp, const u32 *srcp, size_t c) { } } -void FramebufferManagerD3D11::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { - if (!framebuffer->fbo || !useBufferedRendering_) { - ID3D11ShaderResourceView *view = nullptr; - context_->PSSetShaderResources(stage, 1, &view); - gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; - return; - } - - // currentRenderVfb_ will always be set when this is called, except from the GE debugger. - // Let's just not bother with the copy in that case. - bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0; - if (GPUStepping::IsStepping()) { - skipCopy = true; - } - // Currently rendering to this framebuffer. Need to make a copy. - if (!skipCopy && framebuffer == currentRenderVfb_) { - // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); - if (renderCopy) { - VirtualFramebuffer copyInfo = *framebuffer; - copyInfo.fbo = renderCopy; - CopyFramebufferForColorTexture(©Info, framebuffer, flags); - RebindFramebuffer("RebindFramebuffer - BindFramebufferAsColorTexture"); - draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0); - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } - } else if (framebuffer != currentRenderVfb_ || (flags & BINDFBCOLOR_FORCE_SELF) != 0) { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } else { - ERROR_LOG_REPORT_ONCE(d3d11SelfTexture, G3D, "Attempting to texture from target (src=%08x / target=%08x / flags=%d)", framebuffer->fb_address, currentRenderVfb_->fb_address, flags); - // Badness on D3D11 to bind the currently rendered-to framebuffer as a texture. - ID3D11ShaderResourceView *view = nullptr; - context_->PSSetShaderResources(stage, 1, &view); - gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; - return; - } -} - void FramebufferManagerD3D11::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } @@ -420,8 +344,8 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, return; } - float srcXFactor = (float)src->renderWidth / (float)src->bufferWidth; - float srcYFactor = (float)src->renderHeight / (float)src->bufferHeight; + float srcXFactor = src->renderScaleFactor; + float srcYFactor = src->renderScaleFactor; const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2; if (srcBpp != bpp && bpp != 0) { srcXFactor = (srcXFactor * bpp) / srcBpp; @@ -431,8 +355,8 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int srcY1 = srcY * srcYFactor; int srcY2 = (srcY + h) * srcYFactor; - float dstXFactor = (float)dst->renderWidth / (float)dst->bufferWidth; - float dstYFactor = (float)dst->renderHeight / (float)dst->bufferHeight; + float dstXFactor = dst->renderScaleFactor; + float dstYFactor = dst->renderScaleFactor; const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2; if (dstBpp != bpp && bpp != 0) { dstXFactor = (dstXFactor * bpp) / dstBpp; @@ -445,7 +369,7 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, // Direct3D doesn't support rect -> self. Draw::Framebuffer *srcFBO = src->fbo; if (src == dst) { - Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); + Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight); SimpleBlit(tempFBO, dstX1, dstY1, dstX2, dstY2, src->fbo, srcX1, srcY1, srcX2, srcY2, false); srcFBO = tempFBO; } @@ -468,7 +392,3 @@ void FramebufferManagerD3D11::PackDepthbuffer(VirtualFramebuffer *vfb, int x, in void FramebufferManagerD3D11::EndFrame() { } - -void FramebufferManagerD3D11::DeviceLost() { - DestroyAllFBOs(); -} diff --git a/GPU/D3D11/FramebufferManagerD3D11.h b/GPU/D3D11/FramebufferManagerD3D11.h index a8a4089198..dc2bc15e94 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.h +++ b/GPU/D3D11/FramebufferManagerD3D11.h @@ -42,10 +42,6 @@ public: void DrawActiveTexture(float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, int uvRotation, int flags) override; void EndFrame(); - void DeviceLost(); - void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) override; - - void BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); virtual bool NotifyStencilUpload(u32 addr, int size, StencilUpload flags = StencilUpload::NEEDS_CLEAR) override; diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index a8f7b95fa6..f3e738029c 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -129,7 +129,6 @@ void GPU_D3D11::CheckGPUFeatures() { features |= GPU_SUPPORTS_VS_RANGE_CULLING; } features |= GPU_SUPPORTS_BLEND_MINMAX; - features |= GPU_PREFER_CPU_DOWNLOAD; // Accurate depth is required because the Direct3D API does not support inverse Z. // So we cannot incorrectly use the viewport transform as the depth range on Direct3D. @@ -141,6 +140,7 @@ void GPU_D3D11::CheckGPUFeatures() { features |= GPU_SUPPORTS_ANISOTROPY; #endif + features |= GPU_SUPPORTS_DEPTH_TEXTURE; features |= GPU_SUPPORTS_OES_TEXTURE_NPOT; if (draw_->GetDeviceCaps().dualSourceBlend) features |= GPU_SUPPORTS_DUALSOURCE_BLEND; @@ -199,10 +199,12 @@ void GPU_D3D11::DeviceLost() { shaderManagerD3D11_->ClearShaders(); drawEngine_.ClearInputLayoutMap(); textureCacheD3D11_->Clear(false); - framebufferManagerD3D11_->DeviceLost(); + + GPUCommon::DeviceLost(); } void GPU_D3D11::DeviceRestore() { + GPUCommon::DeviceRestore(); // Nothing needed. } diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp index 4678f4ae65..255c547ee9 100644 --- a/GPU/D3D11/ShaderManagerD3D11.cpp +++ b/GPU/D3D11/ShaderManagerD3D11.cpp @@ -34,9 +34,8 @@ #include "GPU/Math3D.h" #include "GPU/GPUState.h" #include "GPU/ge_constants.h" +#include "GPU/Common/VertexShaderGenerator.h" #include "GPU/D3D11/ShaderManagerD3D11.h" -#include "GPU/D3D11/FragmentShaderGeneratorD3D11.h" -#include "GPU/D3D11/VertexShaderGeneratorD3D11.h" #include "GPU/D3D11/D3D11Util.h" D3D11FragmentShader::D3D11FragmentShader(ID3D11Device *device, D3D_FEATURE_LEVEL featureLevel, FShaderID id, const char *code, bool useHWTransform) @@ -90,7 +89,7 @@ std::string D3D11VertexShader::GetShaderString(DebugShaderStringType type) const } ShaderManagerD3D11::ShaderManagerD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context, D3D_FEATURE_LEVEL featureLevel) - : ShaderManagerCommon(draw), device_(device), context_(context), featureLevel_(featureLevel), lastVShader_(nullptr), lastFShader_(nullptr) { + : ShaderManagerCommon(draw), device_(device), context_(context), featureLevel_(featureLevel) { codeBuffer_ = new char[16384]; memset(&ub_base, 0, sizeof(ub_base)); memset(&ub_lights, 0, sizeof(ub_lights)); @@ -208,7 +207,10 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader ** D3D11VertexShader *vs; if (vsIter == vsCache_.end()) { // Vertex shader not in cache. Let's compile it. - GenerateVertexShaderD3D11(VSID, codeBuffer_, featureLevel_ <= D3D_FEATURE_LEVEL_9_3 ? HLSL_D3D11_LEVEL9 : HLSL_D3D11); + std::string genErrorString; + uint32_t attrMask; + uint64_t uniformMask; + GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &attrMask, &uniformMask, &genErrorString); vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, vertType, useHWTransform); vsCache_[VSID] = vs; } else { @@ -220,7 +222,9 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader ** D3D11FragmentShader *fs; if (fsIter == fsCache_.end()) { // Fragment shader not in cache. Let's compile it. - GenerateFragmentShaderD3D11(FSID, codeBuffer_, featureLevel_ <= D3D_FEATURE_LEVEL_9_3 ? HLSL_D3D11_LEVEL9 : HLSL_D3D11); + std::string genErrorString; + uint64_t uniformMask; + GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &uniformMask, &genErrorString); fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform); fsCache_[FSID] = fs; } else { diff --git a/GPU/D3D11/ShaderManagerD3D11.h b/GPU/D3D11/ShaderManagerD3D11.h index abb867a613..fe21710ad9 100644 --- a/GPU/D3D11/ShaderManagerD3D11.h +++ b/GPU/D3D11/ShaderManagerD3D11.h @@ -25,6 +25,7 @@ #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/ShaderId.h" #include "GPU/Common/ShaderUniforms.h" +#include "GPU/Common/FragmentShaderGenerator.h" class D3D11Context; class D3D11PushBuffer; @@ -128,8 +129,8 @@ private: ID3D11Buffer *push_lights; ID3D11Buffer *push_bones; - D3D11FragmentShader *lastFShader_; - D3D11VertexShader *lastVShader_; + D3D11FragmentShader *lastFShader_ = nullptr; + D3D11VertexShader *lastVShader_ = nullptr; FShaderID lastFSID_; VShaderID lastVSID_; diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index 8ef3d2c574..5b0bda982f 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -122,7 +122,7 @@ static const D3D11_LOGIC_OP logicOps[] = { D3D11_LOGIC_OP_SET, }; -void DrawEngineD3D11::ResetShaderBlending() { +void DrawEngineD3D11::ResetFramebufferRead() { if (fboTexBound_) { ID3D11ShaderResourceView *srv = nullptr; context_->PSSetShaderResources(0, 1, &srv); @@ -144,7 +144,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { bool useBufferedRendering = framebufferManager_->UseBufferedRendering(); // Blend if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) { - gstate_c.SetAllowShaderBlend(!g_Config.bDisableSlowFramebufEffects); + gstate_c.SetAllowFramebufferRead(!g_Config.bDisableSlowFramebufEffects); if (gstate.isModeClear()) { keys_.blend.value = 0; // full wipe keys_.blend.blendEnable = false; @@ -157,18 +157,24 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { keys_.blend.value = 0; // Set blend - unless we need to do it in the shader. GenericBlendState blendState; - ConvertBlendState(blendState, gstate_c.allowShaderBlend); - if (blendState.applyShaderBlending) { - if (ApplyShaderBlending()) { - // We may still want to do something about stencil -> alpha. - ApplyStencilReplaceAndLogicOp(blendState.replaceAlphaWithStencil, blendState); + ConvertBlendState(blendState, gstate_c.allowFramebufferRead); + + GenericMaskState maskState; + ConvertMaskState(maskState, gstate_c.allowFramebufferRead); + + if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { + if (ApplyFramebufferRead(&fboTexNeedsBind_)) { + // The shader takes over the responsibility for blending, so recompute. + ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); } else { // Until next time, force it off. - ResetShaderBlending(); - gstate_c.SetAllowShaderBlend(false); + ResetFramebufferRead(); + gstate_c.SetAllowFramebufferRead(false); } - } else if (blendState.resetShaderBlending) { - ResetShaderBlending(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); + } else if (blendState.resetFramebufferRead) { + ResetFramebufferRead(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } if (blendState.enabled) { @@ -180,7 +186,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { keys_.blend.srcAlpha = d3d11BlendFactorLookup[(size_t)blendState.srcAlpha]; keys_.blend.destColor = d3d11BlendFactorLookup[(size_t)blendState.dstColor]; keys_.blend.destAlpha = d3d11BlendFactorLookup[(size_t)blendState.dstAlpha]; - if (blendState.dirtyShaderBlend) { + if (blendState.dirtyShaderBlendFixValues) { gstate_c.Dirty(DIRTY_SHADERBLEND); } dynState_.useBlendColor = blendState.useBlendColor; @@ -203,38 +209,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { } } - // PSP color/alpha mask is per bit but we can only support per byte. - // But let's do that, at least. And let's try a threshold. - bool rmask = (gstate.pmskc & 0xFF) < 128; - bool gmask = ((gstate.pmskc >> 8) & 0xFF) < 128; - bool bmask = ((gstate.pmskc >> 16) & 0xFF) < 128; - bool amask = (gstate.pmska & 0xFF) < 128; - -#ifndef MOBILE_DEVICE - u8 abits = (gstate.pmska >> 0) & 0xFF; - u8 rbits = (gstate.pmskc >> 0) & 0xFF; - u8 gbits = (gstate.pmskc >> 8) & 0xFF; - u8 bbits = (gstate.pmskc >> 16) & 0xFF; - if ((rbits != 0 && rbits != 0xFF) || (gbits != 0 && gbits != 0xFF) || (bbits != 0 && bbits != 0xFF)) { - WARN_LOG_REPORT_ONCE(rgbmask, G3D, "Unsupported RGB mask: r=%02x g=%02x b=%02x", rbits, gbits, bbits); - } - if (abits != 0 && abits != 0xFF) { - // The stencil part of the mask is supported. - WARN_LOG_REPORT_ONCE(amask, G3D, "Unsupported alpha/stencil mask: %02x", abits); - } -#endif - - // Let's not write to alpha if stencil isn't enabled. - if (IsStencilTestOutputDisabled()) { - amask = false; - } else { - // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. - if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { - amask = false; - } - } - - keys_.blend.colorWriteMask = (rmask ? 1 : 0) | (gmask ? 2 : 0) | (bmask ? 4 : 0) | (amask ? 8 : 0); + keys_.blend.colorWriteMask = (maskState.rgba[0] ? 1 : 0) | (maskState.rgba[1] ? 2 : 0) | (maskState.rgba[2] ? 4 : 0) | (maskState.rgba[3] ? 8 : 0); } if (!device1_) { @@ -446,11 +421,11 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { void DrawEngineD3D11::ApplyDrawStateLate(bool applyStencilRef, uint8_t stencilRef) { if (!gstate.isModeClear()) { - if (fboTexNeedBind_) { + if (fboTexNeedsBind_) { framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // No sampler required, we do a plain Load in the pixel shader. fboTexBound_ = true; - fboTexNeedBind_ = false; + fboTexNeedsBind_ = false; } textureCache_->ApplyTexture(); } diff --git a/GPU/D3D11/StencilBufferD3D11.cpp b/GPU/D3D11/StencilBufferD3D11.cpp index 4da469ae61..118dba0dc5 100644 --- a/GPU/D3D11/StencilBufferD3D11.cpp +++ b/GPU/D3D11/StencilBufferD3D11.cpp @@ -23,7 +23,6 @@ #include "Core/Reporting.h" #include "GPU/Common/StencilCommon.h" #include "GPU/D3D11/FramebufferManagerD3D11.h" -#include "GPU/D3D11/FragmentShaderGeneratorD3D11.h" #include "GPU/D3D11/ShaderManagerD3D11.h" #include "GPU/D3D11/TextureCacheD3D11.h" #include "GPU/D3D11/D3D11Util.h" diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index b136e06020..56df0b78f3 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -24,7 +25,7 @@ #include "Core/Reporting.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h" -#include "GPU/D3D11/FragmentShaderGeneratorD3D11.h" +#include "GPU/Common/GPUStateUtils.h" #include "GPU/D3D11/TextureCacheD3D11.h" #include "GPU/D3D11/FramebufferManagerD3D11.h" #include "GPU/D3D11/ShaderManagerD3D11.h" @@ -38,6 +39,12 @@ #include "ext/xxhash.h" #include "Common/Math/math_util.h" +// For depth depal +struct DepthPushConstants { + float z_scale; + float z_offset; + float pad[2]; +}; #define INVALID_TEX (ID3D11ShaderResourceView *)(-1LL) @@ -112,6 +119,10 @@ TextureCacheD3D11::TextureCacheD3D11(Draw::DrawContext *draw) isBgraBackend_ = true; lastBoundTexture = INVALID_TEX; + D3D11_BUFFER_DESC desc{ sizeof(DepthPushConstants), D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE }; + HRESULT hr = device_->CreateBuffer(&desc, nullptr, &depalConstants_); + _dbg_assert_(SUCCEEDED(hr)); + HRESULT result = 0; SetupTextureDecoder(); @@ -120,6 +131,8 @@ TextureCacheD3D11::TextureCacheD3D11(Draw::DrawContext *draw) } TextureCacheD3D11::~TextureCacheD3D11() { + depalConstants_->Release(); + // pFramebufferVertexDecl->Release(); Clear(true); } @@ -354,8 +367,9 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, ID3D11PixelShader *pshader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; bool need_depalettize = IsClutFormat(texFormat); + bool depth = channel == NOTIFY_FB_DEPTH; if (need_depalettize && !g_Config.bDisableSlowFramebufEffects) { - pshader = depalShaderCache_->GetDepalettizePixelShader(clutMode, framebuffer->drawnFormat); + pshader = depalShaderCache_->GetDepalettizePixelShader(clutMode, depth ? GE_FORMAT_DEPTH16 : framebuffer->drawnFormat); } if (pshader) { @@ -363,7 +377,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); ID3D11ShaderResourceView *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_, expand32); - Draw::Framebuffer *depalFBO = framebufferManagerD3D11_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerD3D11_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight); shaderManager_->DirtyLastShader(); // Not sure why or if we need this here - we're not about to actually draw using draw_, just use its framebuffer binds. @@ -381,8 +395,20 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "ApplyTextureFramebuffer_DepalShader"); context_->PSSetShaderResources(3, 1, &clutTexture); context_->PSSetSamplers(3, 1, &stockD3D11.samplerPoint2DWrap); - framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY | BINDFBCOLOR_FORCE_SELF); + draw_->BindFramebufferAsTexture(framebuffer->fbo, 0, depth ? Draw::FB_DEPTH_BIT : Draw::FB_COLOR_BIT, 0); context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DWrap); + + if (depth) { + DepthScaleFactors scaleFactors = GetDepthScaleFactors(); + DepthPushConstants push; + push.z_scale = scaleFactors.scale; + push.z_offset = scaleFactors.offset; + D3D11_MAPPED_SUBRESOURCE map; + context_->Map(depalConstants_, 0, D3D11_MAP_WRITE_DISCARD, 0, &map); + memcpy(map.pData, &push, sizeof(push)); + context_->Unmap(depalConstants_, 0); + context_->PSSetConstantBuffers(0, 1, &depalConstants_); + } shaderApply.Shade(); context_->PSSetShaderResources(0, 1, &nullTexture); // Make D3D11 validation happy. Really of no consequence since we rebind anyway. @@ -395,10 +421,9 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, TexCacheEntry::TexStatus alphaStatus = CheckAlpha(clutBuf_, GetClutDestFormatD3D11(clutFormat), clutTotalColors, clutTotalColors, 1); gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL); } else { - framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET); - gstate_c.SetTextureFullAlpha(gstate.getTextureFormat() == GE_TFMT_5650); - framebufferManagerD3D11_->RebindFramebuffer("RebindFramebuffer - ApplyTextureFramebuffer"); // Probably not necessary. + framebufferManagerD3D11_->RebindFramebuffer("RebindFramebuffer - ApplyTextureFramebuffer"); + framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET); } SamplerCacheKey samplerKey = GetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight); diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index ceaf64b150..b1611936a5 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -70,7 +70,7 @@ protected: private: void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, int scaleFactor, DXGI_FORMAT dstFmt); DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); + static TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, FramebufferNotificationChannel channel) override; @@ -91,6 +91,7 @@ private: SamplerCacheD3D11 samplerCache_; ID3D11ShaderResourceView *lastBoundTexture; + ID3D11Buffer *depalConstants_; int decimationCounter_; int texelsScaledThisFrame_; @@ -99,6 +100,7 @@ private: FramebufferManagerD3D11 *framebufferManagerD3D11_; DepalShaderCacheD3D11 *depalShaderCache_; ShaderManagerD3D11 *shaderManager_; + }; DXGI_FORMAT GetClutDestFormatD3D11(GEPaletteFormat format); diff --git a/GPU/D3D11/VertexShaderGeneratorD3D11.cpp b/GPU/D3D11/VertexShaderGeneratorD3D11.cpp deleted file mode 100644 index 75affc997d..0000000000 --- a/GPU/D3D11/VertexShaderGeneratorD3D11.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2017- 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/. - -#include "GPU/Common/ShaderCommon.h" -#include "GPU/D3D11/VertexShaderGeneratorD3D11.h" -#include "GPU/Directx9/VertexShaderGeneratorDX9.h" - -void GenerateVertexShaderD3D11(const VShaderID &id, char *buffer, ShaderLanguage lang) { - DX9::GenerateVertexShaderHLSL(id, buffer, lang); -} diff --git a/GPU/D3D11/VertexShaderGeneratorD3D11.h b/GPU/D3D11/VertexShaderGeneratorD3D11.h deleted file mode 100644 index fcf6f7f749..0000000000 --- a/GPU/D3D11/VertexShaderGeneratorD3D11.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2017- 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/. - -#pragma once - -#include "GPU/Common/ShaderId.h" - -void GenerateVertexShaderD3D11(const VShaderID &id, char *buffer, ShaderLanguage lang); diff --git a/GPU/Directx9/DepalettizeShaderDX9.cpp b/GPU/Directx9/DepalettizeShaderDX9.cpp index bff0c04933..3e810a7053 100644 --- a/GPU/Directx9/DepalettizeShaderDX9.cpp +++ b/GPU/Directx9/DepalettizeShaderDX9.cpp @@ -52,10 +52,10 @@ VS_OUT main(VS_IN input) { } )"; -DepalShaderCacheDX9::DepalShaderCacheDX9(Draw::DrawContext *draw) : vertexShader_(nullptr) { +DepalShaderCacheDX9::DepalShaderCacheDX9(Draw::DrawContext *draw) { device_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE); std::string errorMessage; - if (!DX9::CompileVertexShader(device_, depalVShaderHLSL, &vertexShader_, nullptr, errorMessage)) { + if (!CompileVertexShaderD3D9(device_, depalVShaderHLSL, &vertexShader_, &errorMessage)) { ERROR_LOG(G3D, "error compling depal vshader: %s", errorMessage.c_str()); } } @@ -152,11 +152,11 @@ LPDIRECT3DPIXELSHADER9 DepalShaderCacheDX9::GetDepalettizePixelShader(uint32_t c char *buffer = new char[2048]; - GenerateDepalShader(buffer, pixelFormat, HLSL_DX9); + GenerateDepalShader(buffer, pixelFormat, HLSL_D3D9); LPDIRECT3DPIXELSHADER9 pshader; std::string errorMessage; - if (!CompilePixelShader(device_, buffer, &pshader, NULL, errorMessage)) { + if (!CompilePixelShaderD3D9(device_, buffer, &pshader, &errorMessage)) { ERROR_LOG(G3D, "Failed to compile depal pixel shader: %s\n\n%s", buffer, errorMessage.c_str()); delete[] buffer; return nullptr; diff --git a/GPU/Directx9/DepalettizeShaderDX9.h b/GPU/Directx9/DepalettizeShaderDX9.h index b6c77aa6f2..1f9ad4a6b7 100644 --- a/GPU/Directx9/DepalettizeShaderDX9.h +++ b/GPU/Directx9/DepalettizeShaderDX9.h @@ -54,9 +54,9 @@ public: private: LPDIRECT3DDEVICE9 device_; - LPDIRECT3DVERTEXSHADER9 vertexShader_; + LPDIRECT3DVERTEXSHADER9 vertexShader_ = nullptr; std::map cache_; std::map texCache_; }; -} // namespace \ No newline at end of file +} // namespace diff --git a/GPU/Directx9/DrawEngineDX9.h b/GPU/Directx9/DrawEngineDX9.h index 3ddf99b79b..f0963beb91 100644 --- a/GPU/Directx9/DrawEngineDX9.h +++ b/GPU/Directx9/DrawEngineDX9.h @@ -26,7 +26,6 @@ #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/GPUStateUtils.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" struct DecVtxFormat; struct UVScale; @@ -151,7 +150,7 @@ private: void ApplyDrawState(int prim); void ApplyDrawStateLate(); - void ResetShaderBlending(); + void ResetFramebufferRead(); IDirect3DVertexDeclaration9 *SetupDecFmtForDraw(VSShader *vshader, const DecVtxFormat &decFmt, u32 pspFmt); diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index 0c9c2d5720..9d3b631e10 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -31,7 +31,7 @@ #include "Common/GPU/D3D9/D3D9StateCache.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/PresentationCommon.h" -#include "GPU/Common/ShaderTranslation.h" +#include "Common/GPU/ShaderTranslation.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Directx9/FramebufferManagerDX9.h" #include "GPU/Directx9/ShaderManagerDX9.h" @@ -52,7 +52,7 @@ static const char *vscode = R"( struct VS_IN { float4 ObjPos : POSITION; float2 Uv : TEXCOORD0; -};" +}; struct VS_OUT { float4 ProjPos : POSITION; float2 Uv : TEXCOORD0; @@ -88,11 +88,11 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { device_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE); deviceEx_ = (LPDIRECT3DDEVICE9)draw->GetNativeObject(Draw::NativeObject::DEVICE_EX); std::string errorMsg; - if (!CompileVertexShader(device_, vscode, &pFramebufferVertexShader, nullptr, errorMsg)) { + if (!CompileVertexShaderD3D9(device_, vscode, &pFramebufferVertexShader, &errorMsg)) { OutputDebugStringA(errorMsg.c_str()); } - if (!CompilePixelShader(device_, pscode, &pFramebufferPixelShader, nullptr, errorMsg)) { + if (!CompilePixelShaderD3D9(device_, pscode, &pFramebufferPixelShader, &errorMsg)) { OutputDebugStringA(errorMsg.c_str()); if (pFramebufferVertexShader) { pFramebufferVertexShader->Release(); @@ -116,7 +116,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { ShaderTranslationInit(); - presentation_->SetLanguage(HLSL_DX9); + presentation_->SetLanguage(HLSL_D3D9); preferredPixelsFormat_ = Draw::DataFormat::B8G8R8A8_UNORM; } @@ -228,83 +228,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { device_->SetVertexShader(pFramebufferVertexShader); } - void FramebufferManagerDX9::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { - if (!useBufferedRendering_ || !vfb->fbo) { - return; - } - - // Technically, we should at this point re-interpret the bytes of the old format to the new. - // That might get tricky, and could cause unnecessary slowness in some games. - // For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts. - // (it uses 565 to write zeros to the buffer, then 4444 to actually render the shadow.) - // - // The best way to do this may ultimately be to create a new FBO (combine with any resize?) - // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex - // to exactly reproduce in 4444 and 8888 formats. - - if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "ReformatFramebuffer"); - - dxstate.scissorTest.disable(); - dxstate.depthWrite.set(FALSE); - dxstate.colorMask.set(false, false, false, true); - dxstate.stencilFunc.set(D3DCMP_ALWAYS, 0, 0); - dxstate.stencilMask.set(0xFF); - gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); - - float coord[20] = { - -1.0f,-1.0f,0, 0,0, - 1.0f,-1.0f,0, 0,0, - 1.0f,1.0f,0, 0,0, - -1.0f,1.0f,0, 0,0, - }; - - dxstate.cullMode.set(false, false); - device_->SetVertexDeclaration(pFramebufferVertexDecl); - device_->SetPixelShader(pFramebufferPixelShader); - device_->SetVertexShader(pFramebufferVertexShader); - shaderManagerDX9_->DirtyLastShader(); - device_->SetTexture(0, nullTex_); - - D3DVIEWPORT9 vp{ 0, 0, (DWORD)vfb->renderWidth, (DWORD)vfb->renderHeight, 0.0f, 1.0f }; - device_->SetViewport(&vp); - - // This should clear stencil and alpha without changing the other colors. - HRESULT hr = device_->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, coord, 5 * sizeof(float)); - if (FAILED(hr)) { - ERROR_LOG_REPORT(G3D, "ReformatFramebufferFrom() failed: %08x", hr); - } - dxstate.viewport.restore(); - - textureCache_->ForgetLastTexture(); - } - } - - static void CopyPixelDepthOnly(u32 *dstp, const u32 *srcp, size_t c) { - size_t x = 0; - -#ifdef _M_SSE - size_t sseSize = (c / 4) * 4; - const __m128i srcMask = _mm_set1_epi32(0x00FFFFFF); - const __m128i dstMask = _mm_set1_epi32(0xFF000000); - __m128i *dst = (__m128i *)dstp; - const __m128i *src = (const __m128i *)srcp; - - for (; x < sseSize; x += 4) { - const __m128i bits24 = _mm_and_si128(_mm_load_si128(src), srcMask); - const __m128i bits8 = _mm_and_si128(_mm_load_si128(dst), dstMask); - _mm_store_si128(dst, _mm_or_si128(bits24, bits8)); - dst++; - src++; - } -#endif - - // Copy the remaining pixels that didn't fit in SSE. - for (; x < c; ++x) { - memcpy(dstp + x, srcp + x, 3); - } - } - LPDIRECT3DSURFACE9 FramebufferManagerDX9::GetOffscreenSurface(LPDIRECT3DSURFACE9 similarSurface, VirtualFramebuffer *vfb) { D3DSURFACE_DESC desc = {}; HRESULT hr = similarSurface->GetDesc(&desc); @@ -336,41 +259,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return offscreen; } - void FramebufferManagerDX9::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { - if (framebuffer == NULL) { - framebuffer = currentRenderVfb_; - } - - if (!framebuffer->fbo || !useBufferedRendering_) { - device_->SetTexture(stage, nullptr); - gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; - return; - } - - // currentRenderVfb_ will always be set when this is called, except from the GE debugger. - // Let's just not bother with the copy in that case. - bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0; - if (GPUStepping::IsStepping()) { - skipCopy = true; - } - if (!skipCopy && framebuffer == currentRenderVfb_) { - // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); - if (renderCopy) { - VirtualFramebuffer copyInfo = *framebuffer; - copyInfo.fbo = renderCopy; - - CopyFramebufferForColorTexture(©Info, framebuffer, flags); - RebindFramebuffer("RebindFramebuffer - BindFramebufferAsColorTexture"); - draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0); - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } - } - void FramebufferManagerDX9::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } @@ -383,8 +271,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return; } - float srcXFactor = (float)src->renderWidth / (float)src->bufferWidth; - float srcYFactor = (float)src->renderHeight / (float)src->bufferHeight; + float srcXFactor = (float)src->renderScaleFactor; + float srcYFactor = (float)src->renderScaleFactor; const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2; if (srcBpp != bpp && bpp != 0) { srcXFactor = (srcXFactor * bpp) / srcBpp; @@ -394,8 +282,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { int srcY1 = srcY * srcYFactor; int srcY2 = (srcY + h) * srcYFactor; - float dstXFactor = (float)dst->renderWidth / (float)dst->bufferWidth; - float dstYFactor = (float)dst->renderHeight / (float)dst->bufferHeight; + float dstXFactor = (float)dst->renderScaleFactor; + float dstYFactor = (float)dst->renderScaleFactor; const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2; if (dstBpp != bpp && bpp != 0) { dstXFactor = (dstXFactor * bpp) / dstBpp; @@ -408,7 +296,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { // Direct3D 9 doesn't support rect -> self. Draw::Framebuffer *srcFBO = src->fbo; if (src == dst) { - Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight, (Draw::FBColorDepth)src->colorDepth); + Draw::Framebuffer *tempFBO = GetTempFBO(TempFBO::BLIT, src->renderWidth, src->renderHeight); bool result = draw_->BlitFramebuffer( src->fbo, srcX1, srcY1, srcX2, srcY2, tempFBO, dstX1, dstY1, dstX2, dstY2, @@ -559,10 +447,6 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { void FramebufferManagerDX9::EndFrame() { } - void FramebufferManagerDX9::DeviceLost() { - DestroyAllFBOs(); - } - void FramebufferManagerDX9::DecimateFBOs() { FramebufferManagerCommon::DecimateFBOs(); for (auto it = offscreenSurfaces_.begin(); it != offscreenSurfaces_.end(); ) { @@ -606,7 +490,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { // Let's resize. We must stretch to a render target first. w = vfb->width * maxRes; h = vfb->height * maxRes; - tempFBO = draw_->CreateFramebuffer({ w, h, 1, 1, false, Draw::FBO_8888 }); + tempFBO = draw_->CreateFramebuffer({ w, h, 1, 1, false }); if (draw_->BlitFramebuffer(vfb->fbo, 0, 0, vfb->renderWidth, vfb->renderHeight, tempFBO, 0, 0, w, h, Draw::FB_COLOR_BIT, g_Config.iBufFilter == SCALE_LINEAR ? Draw::FB_BLIT_LINEAR : Draw::FB_BLIT_NEAREST, "GetFramebuffer")) { renderTarget = (LPDIRECT3DSURFACE9)draw_->GetFramebufferAPITexture(tempFBO, Draw::FB_COLOR_BIT | Draw::FB_SURFACE_BIT, 0); } diff --git a/GPU/Directx9/FramebufferManagerDX9.h b/GPU/Directx9/FramebufferManagerDX9.h index 3a8578599a..ac1dbaa484 100644 --- a/GPU/Directx9/FramebufferManagerDX9.h +++ b/GPU/Directx9/FramebufferManagerDX9.h @@ -47,10 +47,6 @@ public: void DestroyAllFBOs(); void EndFrame(); - void DeviceLost(); - void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) override; - - void BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); virtual bool NotifyStencilUpload(u32 addr, int size, StencilUpload flags = StencilUpload::NEEDS_CLEAR) override; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 439b443cd7..ec9cf20385 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -164,7 +164,6 @@ void GPU_DX9::CheckGPUFeatures() { features |= GPU_SUPPORTS_16BIT_FORMATS; features |= GPU_SUPPORTS_BLEND_MINMAX; features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; - features |= GPU_PREFER_CPU_DOWNLOAD; // Accurate depth is required because the Direct3D API does not support inverse Z. // So we cannot incorrectly use the viewport transform as the depth range on Direct3D. @@ -243,13 +242,13 @@ void GPU_DX9::BuildReportingInfo() { void GPU_DX9::DeviceLost() { // Simply drop all caches and textures. - // FBOs appear to survive? Or no? shaderManagerDX9_->ClearCache(false); textureCacheDX9_->Clear(false); - framebufferManagerDX9_->DeviceLost(); + GPUCommon::DeviceLost(); } void GPU_DX9::DeviceRestore() { + GPUCommon::DeviceRestore(); // Nothing needed. } diff --git a/GPU/Directx9/PixelShaderGeneratorDX9.cpp b/GPU/Directx9/PixelShaderGeneratorDX9.cpp deleted file mode 100644 index 09fa3c9a73..0000000000 --- a/GPU/Directx9/PixelShaderGeneratorDX9.cpp +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include - -#include "Core/Reporting.h" -#include "Core/Config.h" -#include "Core/System.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" -#include "GPU/ge_constants.h" -#include "GPU/Common/GPUStateUtils.h" -#include "GPU/GPUState.h" -#include "GPU/Common/ShaderUniforms.h" - -#define WRITE p+=sprintf - -// #define DEBUG_SHADER - -namespace DX9 { - -// Missing: Z depth range -// Also, logic ops etc, of course, as they are not supported in DX9. -bool GenerateFragmentShaderHLSL(const FShaderID &id, char *buffer, ShaderLanguage lang) { - char *p = buffer; - - bool lmode = id.Bit(FS_BIT_LMODE); - bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); - bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); - bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); - - bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); - bool enableColorTest = id.Bit(FS_BIT_COLOR_TEST); - bool colorTestAgainstZero = id.Bit(FS_BIT_COLOR_AGAINST_ZERO); - bool enableColorDoubling = id.Bit(FS_BIT_COLOR_DOUBLE); - bool doTextureProjection = id.Bit(FS_BIT_DO_TEXTURE_PROJ); - bool doTextureAlpha = id.Bit(FS_BIT_TEXALPHA); - bool doFlatShading = id.Bit(FS_BIT_FLATSHADE); - bool isModeClear = id.Bit(FS_BIT_CLEARMODE); - - bool bgraTexture = id.Bit(FS_BIT_BGRA_TEXTURE); - - GEComparison alphaTestFunc = (GEComparison)id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3); - GEComparison colorTestFunc = (GEComparison)id.Bits(FS_BIT_COLOR_TEST_FUNC, 2); - bool needShaderTexClamp = id.Bit(FS_BIT_SHADER_TEX_CLAMP); - - ReplaceBlendType replaceBlend = static_cast(id.Bits(FS_BIT_REPLACE_BLEND, 3)); - ReplaceAlphaType stencilToAlpha = static_cast(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)); - - GETexFunc texFunc = (GETexFunc)id.Bits(FS_BIT_TEXFUNC, 3); - bool textureAtOffset = id.Bit(FS_BIT_TEXTURE_AT_OFFSET); - - GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); - GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); - GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); - - StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4); - - if (lang == HLSL_DX9) { - if (doTexture) - WRITE(p, "sampler tex : register(s0);\n"); - if (!isModeClear && replaceBlend > REPLACE_BLEND_STANDARD) { - if (replaceBlend == REPLACE_BLEND_COPY_FBO) { - WRITE(p, "float2 u_fbotexSize : register(c%i);\n", CONST_PS_FBOTEXSIZE); - WRITE(p, "sampler fbotex : register(s1);\n"); - } - if (replaceBlendFuncA >= GE_SRCBLEND_FIXA) { - WRITE(p, "float3 u_blendFixA : register(c%i);\n", CONST_PS_BLENDFIXA); - } - if (replaceBlendFuncB >= GE_DSTBLEND_FIXB) { - WRITE(p, "float3 u_blendFixB : register(c%i);\n", CONST_PS_BLENDFIXB); - } - } - if (gstate_c.needShaderTexClamp && doTexture) { - WRITE(p, "float4 u_texclamp : register(c%i);\n", CONST_PS_TEXCLAMP); - if (textureAtOffset) { - WRITE(p, "float2 u_texclampoff : register(c%i);\n", CONST_PS_TEXCLAMPOFF); - } - } - - if (enableAlphaTest || enableColorTest) { - WRITE(p, "float4 u_alphacolorref : register(c%i);\n", CONST_PS_ALPHACOLORREF); - WRITE(p, "float4 u_alphacolormask : register(c%i);\n", CONST_PS_ALPHACOLORMASK); - } - if (stencilToAlpha && replaceAlphaWithStencilType == STENCIL_VALUE_UNIFORM) { - WRITE(p, "float u_stencilReplaceValue : register(c%i);\n", CONST_PS_STENCILREPLACE); - } - if (doTexture && texFunc == GE_TEXFUNC_BLEND) { - WRITE(p, "float3 u_texenv : register(c%i);\n", CONST_PS_TEXENV); - } - if (enableFog) { - WRITE(p, "float3 u_fogcolor : register(c%i);\n", CONST_PS_FOGCOLOR); - } - } else { - WRITE(p, "SamplerState samp : register(s0);\n"); - WRITE(p, "Texture2D tex : register(t0);\n"); - if (!isModeClear && replaceBlend > REPLACE_BLEND_STANDARD) { - if (replaceBlend == REPLACE_BLEND_COPY_FBO) { - // No sampler required, we Load - WRITE(p, "Texture2D fboTex : register(t1);\n"); - } - } - WRITE(p, "cbuffer base : register(b0) {\n%s};\n", cb_baseStr); - } - - if (enableAlphaTest) { - if (lang == HLSL_D3D11) { - WRITE(p, "int roundAndScaleTo255i(float x) { return int(floor(x * 255.0f + 0.5f)); }\n"); - } else { - // D3D11 level 9 gets to take this path. - WRITE(p, "float roundAndScaleTo255f(float x) { return floor(x * 255.0f + 0.5f); }\n"); - } - } - if (enableColorTest) { - if (lang == HLSL_D3D11) { - WRITE(p, "uint3 roundAndScaleTo255iv(float3 x) { return uint3(floor(x * 255.0f + 0.5f)); }\n"); - } else { - WRITE(p, "float3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n"); - } - } - - WRITE(p, "struct PS_IN {\n"); - if (doTexture) { - WRITE(p, " float3 v_texcoord: TEXCOORD0;\n"); - } - const char *colorInterpolation = doFlatShading && lang == HLSL_D3D11 ? "nointerpolation " : ""; - WRITE(p, " %sfloat4 v_color0: COLOR0;\n", colorInterpolation); - if (lmode) { - WRITE(p, " float3 v_color1: COLOR1;\n"); - } - if (enableFog) { - WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); - } - if ((lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) && ((replaceBlend == REPLACE_BLEND_COPY_FBO) || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT))) { - WRITE(p, " float4 pixelPos : SV_POSITION;\n"); - } - WRITE(p, "};\n"); - - if (lang == HLSL_DX9) { - WRITE(p, "float4 main( PS_IN In ) : COLOR {\n"); - } else { - WRITE(p, "struct PS_OUT {\n"); - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { - WRITE(p, " float4 target : SV_Target0;\n"); - WRITE(p, " float4 target1 : SV_Target1;\n"); - } - else { - WRITE(p, " float4 target : SV_Target;\n"); - } - if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { - WRITE(p, " float depth : SV_DEPTH;\n"); - } - WRITE(p, "};\n"); - WRITE(p, "PS_OUT main( PS_IN In ) {\n"); - WRITE(p, " PS_OUT outfragment;\n"); - } - - if (isModeClear) { - // Clear mode does not allow any fancy shading. - WRITE(p, " float4 v = In.v_color0;\n"); - } else { - const char *secondary = ""; - // Secondary color for specular on top of texture - if (lmode) { - WRITE(p, " float4 s = float4(In.v_color1, 0);\n"); - secondary = " + s"; - } else { - secondary = ""; - } - - if (doTexture) { - const char *texcoord = "In.v_texcoord"; - // TODO: Not sure the right way to do this for projection. - if (needShaderTexClamp) { - // We may be clamping inside a larger surface (tex = 64x64, buffer=480x272). - // We may also be wrapping in such a surface, or either one in a too-small surface. - // Obviously, clamping to a smaller surface won't work. But better to clamp to something. - std::string ucoord = "In.v_texcoord.x"; - std::string vcoord = "In.v_texcoord.y"; - if (doTextureProjection) { - ucoord = "(In.v_texcoord.x / In.v_texcoord.z)"; - vcoord = "(In.v_texcoord.y / In.v_texcoord.z)"; - } - - if (id.Bit(FS_BIT_CLAMP_S)) { - ucoord = "clamp(" + ucoord + ", u_texclamp.z, u_texclamp.x - u_texclamp.z)"; - } else { - ucoord = "fmod(" + ucoord + ", u_texclamp.x)"; - } - if (id.Bit(FS_BIT_CLAMP_T)) { - vcoord = "clamp(" + vcoord + ", u_texclamp.w, u_texclamp.y - u_texclamp.w)"; - } else { - vcoord = "fmod(" + vcoord + ", u_texclamp.y)"; - } - if (textureAtOffset) { - ucoord = "(" + ucoord + " + u_texclampoff.x)"; - vcoord = "(" + vcoord + " + u_texclampoff.y)"; - } - - WRITE(p, " float2 fixedcoord = float2(%s, %s);\n", ucoord.c_str(), vcoord.c_str()); - texcoord = "fixedcoord"; - // We already projected it. - doTextureProjection = false; - } - - if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - if (doTextureProjection) { - WRITE(p, " float4 t = tex.Sample(samp, In.v_texcoord.xy / In.v_texcoord.z)%s;\n", bgraTexture ? ".bgra" : ""); - } else { - WRITE(p, " float4 t = tex.Sample(samp, %s.xy)%s;\n", texcoord, bgraTexture ? ".bgra" : ""); - } - } else { - if (doTextureProjection) { - WRITE(p, " float4 t = tex2Dproj(tex, float4(In.v_texcoord.x, In.v_texcoord.y, 0, In.v_texcoord.z))%s;\n", bgraTexture ? ".bgra" : ""); - } else { - WRITE(p, " float4 t = tex2D(tex, %s.xy)%s;\n", texcoord, bgraTexture ? ".bgra" : ""); - } - } - WRITE(p, " float4 p = In.v_color0;\n"); - - if (doTextureAlpha) { // texfmt == RGBA - switch (texFunc) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " float4 v = p * t%s;\n", secondary); break; - case GE_TEXFUNC_DECAL: - WRITE(p, " float4 v = float4(lerp(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); break; - case GE_TEXFUNC_BLEND: - WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); break; - case GE_TEXFUNC_REPLACE: - WRITE(p, " float4 v = t%s;\n", secondary); break; - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); break; - default: - WRITE(p, " float4 v = p;\n"); break; - } - - } else { // texfmt == RGB - switch (texFunc) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " float4 v = float4(t.rgb * p.rgb, p.a)%s;\n", secondary); break; - case GE_TEXFUNC_DECAL: - WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; - case GE_TEXFUNC_BLEND: - WRITE(p, " float4 v = float4(lerp(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); break; - case GE_TEXFUNC_REPLACE: - WRITE(p, " float4 v = float4(t.rgb, p.a)%s;\n", secondary); break; - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " float4 v = float4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; - default: - WRITE(p, " float4 v = p;\n"); break; - } - } - - if (enableColorDoubling) { - // This happens before fog is applied. - WRITE(p, " v.rgb = clamp(v.rgb * 2.0, 0.0, 1.0);\n"); - } - } else { - // No texture mapping - WRITE(p, " float4 v = In.v_color0 %s;\n", secondary); - } - - if (enableAlphaTest) { - if (alphaTestAgainstZero) { - // When testing against 0 (extremely common), we can avoid some math. - // 0.002 is approximately half of 1.0 / 255.0. - if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { - WRITE(p, " clip(v.a - 0.002);\n"); - } else if (alphaTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. Happens sometimes, actually... - WRITE(p, " clip(-v.a + 0.002);\n"); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, " clip(-1);\n"); - } - } else { - const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; // never/always don't make sense - if (alphaTestFuncs[alphaTestFunc][0] != '#') { - // TODO: Rewrite this to use clip() appropriately (like, clip(v.a - u_alphacolorref.a)) - if (lang == HLSL_D3D11) { - WRITE(p, " if ((roundAndScaleTo255i(v.a) & u_alphacolormask.a) %s u_alphacolorref.a) discard;\n", alphaTestFuncs[alphaTestFunc]); - } else { - // TODO: Use a texture to lookup bitwise ops? - WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) clip(-1);\n", alphaTestFuncs[alphaTestFunc]); - } - } else { - // This means NEVER. See above. - WRITE(p, lang == HLSL_DX9 ? " clip(-1);\n" : " discard;\n"); - } - } - } - - if (enableFog) { - WRITE(p, " float fogCoef = clamp(In.v_fogdepth, 0.0, 1.0);\n"); - WRITE(p, " v = lerp(float4(u_fogcolor, v.a), v, fogCoef);\n"); - } - - if (enableColorTest) { - if (colorTestAgainstZero) { - // When testing against 0 (common), we can avoid some math. - // 0.002 is approximately half of 1.0 / 255.0. - if (colorTestFunc == GE_COMP_NOTEQUAL) { - WRITE(p, " if (v.r < 0.002 && v.g < 0.002 && v.b < 0.002) clip(-1);\n"); - } else if (colorTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. - WRITE(p, " if (v.r > 0.002 || v.g > 0.002 || v.b > 0.002) clip(-1);\n"); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, lang == HLSL_DX9 ? " clip(-1);\n" : " discard;\n"); - } - } else { - const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; // never/always don't make sense - if (colorTestFuncs[colorTestFunc][0] != '#') { - const char *test = colorTestFuncs[colorTestFunc]; - if (lang == HLSL_D3D11) { - WRITE(p, " uint3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " uint3 v_masked = v_scaled & u_alphacolormask.rgb;\n"); - WRITE(p, " uint3 colorTestRef = u_alphacolorref.rgb & u_alphacolormask.rgb;\n"); - // We have to test the components separately, or we get incorrect results. See #10629. - WRITE(p, " if (v_masked.r %s colorTestRef.r && v_masked.g %s colorTestRef.g && v_masked.b %s colorTestRef.b) discard;\n", test, test, test); - } else { - // TODO: Use a texture to lookup bitwise ops instead? - WRITE(p, " float3 colortest = roundAndScaleTo255v(v.rgb);\n"); - WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) clip(-1);\n", test, test, test); - } - } - else { - WRITE(p, lang == HLSL_DX9 ? " clip(-1);\n" : " discard;\n"); - } - } - } - - if (replaceBlend == REPLACE_BLEND_2X_SRC) { - WRITE(p, " v.rgb = v.rgb * 2.0;\n"); - } - - if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - const char *srcFactor = "ERROR"; - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "float3(v.a, v.a, v.a)"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "float3(1.0 - v.a, 1.0 - v.a, 1.0 - v.a)"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "float3(v.a * 2.0, v.a * 2.0, v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "float3(1.0 - v.a * 2.0, 1.0 - v.a * 2.0, 1.0 - v.a * 2.0)"; break; - // PRE_SRC for REPLACE_BLEND_PRE_SRC_2X_ALPHA means "double the src." - // It's close to the same, but clamping can still be an issue. - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "float3(2.0, 2.0, 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; - default: srcFactor = "u_blendFixA"; break; - } - - WRITE(p, " v.rgb = v.rgb * %s;\n", srcFactor); - } - - if ((lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) && replaceBlend == REPLACE_BLEND_COPY_FBO) { - WRITE(p, " float4 destColor = fboTex.Load(int3((int)In.pixelPos.x, (int)In.pixelPos.y, 0));\n"); - - const char *srcFactor = "float3(1.0)"; - const char *dstFactor = "float3(0.0)"; - - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(float3(1.0, 1.0, 1.0) - destColor.rgb)"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "v.aaa"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "float3(1.0, 1.0, 1.0) - v.aaa"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "float3(destColor.aaa)"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "float3(1.0, 1.0, 1.0) - destColor.aaa"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "v.aaa * 2.0"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "float3(1.0, 1.0, 1.0) - v.aaa * 2.0"; break; - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "destColor.aaa * 2.0"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "float3(1.0, 1.0, 1.0) - destColor.aaa * 2.0"; break; - case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; - default: srcFactor = "u_blendFixA"; break; - } - switch (replaceBlendFuncB) { - case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; - case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(float3(1.0, 1.0, 1.0) - v.rgb)"; break; - case GE_DSTBLEND_SRCALPHA: dstFactor = "v.aaa"; break; - case GE_DSTBLEND_INVSRCALPHA: dstFactor = "float3(1.0, 1.0, 1.0) - v.aaa"; break; - case GE_DSTBLEND_DSTALPHA: dstFactor = "destColor.aaa"; break; - case GE_DSTBLEND_INVDSTALPHA: dstFactor = "float3(1.0, 1.0, 1.0) - destColor.aaa"; break; - case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "v.aaa * 2.0"; break; - case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "float3(1.0, 1.0, 1.0) - v.aaa * 2.0"; break; - case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "destColor.aaa * 2.0"; break; - case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "float3(1.0, 1.0, 1.0) - destColor.aaa * 2.0"; break; - case GE_DSTBLEND_FIXB: dstFactor = "u_blendFixB"; break; - default: srcFactor = "u_blendFixB"; break; - } - - switch (replaceBlendEq) { - case GE_BLENDMODE_MUL_AND_ADD: - WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT: - WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: - WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", dstFactor, srcFactor); - break; - case GE_BLENDMODE_MIN: - WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_MAX: - WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_ABSDIFF: - WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); - break; - } - } - - // Can do REPLACE_BLEND_COPY_FBO in ps_2_0, but need to apply viewport in the vertex shader - // so that we can have the output position here to sample the texture at. - - if (replaceBlend == REPLACE_BLEND_2X_ALPHA || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - WRITE(p, " v.a = v.a * 2.0;\n"); - } - } - - std::string replacedAlpha = "0.0"; - char replacedAlphaTemp[64] = ""; - if (stencilToAlpha != REPLACE_ALPHA_NO) { - switch (replaceAlphaWithStencilType) { - case STENCIL_VALUE_UNIFORM: - replacedAlpha = "u_stencilReplaceValue"; - break; - - case STENCIL_VALUE_ZERO: - replacedAlpha = "0.0"; - break; - - case STENCIL_VALUE_ONE: - case STENCIL_VALUE_INVERT: - // In invert, we subtract by one, but we want to output one here. - replacedAlpha = "1.0"; - break; - - case STENCIL_VALUE_INCR_4: - case STENCIL_VALUE_DECR_4: - // We're adding/subtracting, just by the smallest value in 4-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 15.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_INCR_8: - case STENCIL_VALUE_DECR_8: - // We're adding/subtracting, just by the smallest value in 8-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 255.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_KEEP: - // Do nothing. We'll mask out the alpha using color mask. - break; - } - } - - switch (stencilToAlpha) { - case REPLACE_ALPHA_YES: - WRITE(p, " v.a = %s;\n", replacedAlpha.c_str()); - break; - - case REPLACE_ALPHA_NO: - // Do nothing, v is already fine. - break; - } - - LogicOpReplaceType replaceLogicOpType = (LogicOpReplaceType)id.Bits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2); - switch (replaceLogicOpType) { - case LOGICOPTYPE_ONE: - WRITE(p, " v.rgb = float3(1.0, 1.0, 1.0);\n"); - break; - case LOGICOPTYPE_INVERT: - WRITE(p, " v.rgb = float3(1.0, 1.0, 1.0) - v.rgb;\n"); - break; - case LOGICOPTYPE_NORMAL: - break; - } - - if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { - const double scale = DepthSliceFactor() * 65535.0; - - WRITE(p, " float z = In.pixelPos.z;\n"); - if (gstate_c.Supports(GPU_SUPPORTS_ACCURATE_DEPTH)) { - // We center the depth with an offset, but only its fraction matters. - // When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0. - if (((int)(DepthSliceFactor() - 1.0f) & 1) == 1) { - WRITE(p, " z = (floor((z * %f) - (1.0 / 2.0)) + (1.0 / 2.0)) * (1.0 / %f);\n", scale, scale); - } else { - WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale); - } - } else { - WRITE(p, " z = (1.0/65535.0) * floor(z * 65535.0);\n"); - } - if (PSP_CoreParameter().compat.flags().DecreaseFragmentDepthAccuracy) { - WRITE(p, " z /= 2000.0;\n"); // Nasty hack to completely remove flicker in Heroes Phantasia - } - WRITE(p, " outfragment.depth = z;\n"); - } - - if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { - WRITE(p, " outfragment.target = float4(v.rgb, %s);\n", replacedAlpha.c_str()); - WRITE(p, " outfragment.target1 = float4(0.0, 0.0, 0.0, v.a);\n"); - WRITE(p, " return outfragment;\n"); - } - else { - WRITE(p, " outfragment.target = v;\n"); - WRITE(p, " return outfragment;\n"); - } - } else { - WRITE(p, " return v;\n"); - } - WRITE(p, "}\n"); - return true; -} - -}; diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index 0e0a24ffba..578b805f6e 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -16,7 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #ifdef _WIN32 -#define SHADERLOG +//#define SHADERLOG #endif #include @@ -41,6 +41,7 @@ #include "GPU/GPUState.h" #include "GPU/ge_constants.h" #include "GPU/Common/ShaderUniforms.h" +#include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Directx9/ShaderManagerDX9.h" #include "GPU/Directx9/DrawEngineDX9.h" #include "GPU/Directx9/FramebufferManagerDX9.h" @@ -57,7 +58,7 @@ PSShader::PSShader(LPDIRECT3DDEVICE9 device, FShaderID id, const char *code) : i bool success; std::string errorMessage; - success = CompilePixelShader(device, code, &shader, NULL, errorMessage); + success = CompilePixelShaderD3D9(device, code, &shader, &errorMessage); if (!errorMessage.empty()) { if (success) { @@ -107,7 +108,7 @@ VSShader::VSShader(LPDIRECT3DDEVICE9 device, VShaderID id, const char *code, boo bool success; std::string errorMessage; - success = CompileVertexShader(device, code, &shader, NULL, errorMessage); + success = CompileVertexShaderD3D9(device, code, &shader, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); @@ -223,13 +224,6 @@ void ShaderManagerDX9::VSSetColorUniform3ExtraFloat(int creg, u32 color, float e device_->SetVertexShaderConstantF(creg, col, 1); } -// Utility -void ShaderManagerDX9::VSSetMatrix4x3(int creg, const float *m4x3) { - float m4x4[16]; - ConvertMatrix4x3To4x4Transposed(m4x4, m4x3); - device_->SetVertexShaderConstantF(creg, m4x4, 4); -} - void ShaderManagerDX9::VSSetMatrix4x3_3(int creg, const float *m4x3) { float m3x4[12]; ConvertMatrix4x3To3x4Transposed(m3x4, m4x3); @@ -237,9 +231,7 @@ void ShaderManagerDX9::VSSetMatrix4x3_3(int creg, const float *m4x3) { } void ShaderManagerDX9::VSSetMatrix(int creg, const float* pMatrix) { - float transp[16]; - Transpose4x4(transp, pMatrix); - device_->SetVertexShaderConstantF(creg, transp, 4); + device_->SetVertexShaderConstantF(creg, pMatrix, 4); } // Depth in ogl is between -1;1 we need between 0;1 and optionally reverse it @@ -511,8 +503,8 @@ void ShaderManagerDX9::VSUpdateUniforms(u64 dirtyUniforms) { } ShaderManagerDX9::ShaderManagerDX9(Draw::DrawContext *draw, LPDIRECT3DDEVICE9 device) - : ShaderManagerCommon(draw), device_(device), lastVShader_(nullptr), lastPShader_(nullptr) { - codeBuffer_ = new char[16384]; + : ShaderManagerCommon(draw), device_(device) { + codeBuffer_ = new char[32768]; } ShaderManagerDX9::~ShaderManagerDX9() { @@ -585,15 +577,23 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat } VSCache::iterator vsIter = vsCache_.find(VSID); - VSShader *vs; + VSShader *vs = nullptr; if (vsIter == vsCache_.end()) { // Vertex shader not in cache. Let's compile it. - GenerateVertexShaderHLSL(VSID, codeBuffer_); - vs = new VSShader(device_, VSID, codeBuffer_, useHWTransform); - - if (vs->Failed()) { + std::string genErrorString; + uint32_t attrMask; + uint64_t uniformMask; + if (GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &attrMask, &uniformMask, &genErrorString)) { + vs = new VSShader(device_, VSID, codeBuffer_, useHWTransform); + } + if (!vs || vs->Failed()) { auto gr = GetI18NCategory("Graphics"); - ERROR_LOG(G3D, "Shader compilation failed, falling back to software transform"); + if (!vs) { + // TODO: Report this? + ERROR_LOG(G3D, "Shader generation failed, falling back to software transform"); + } else { + ERROR_LOG(G3D, "Shader compilation failed, falling back to software transform"); + } if (!g_Config.bHideSlowWarnings) { host->NotifyUserMessage(gr->T("hardware transform error - falling back to software"), 2.5f, 0xFF3030FF); } @@ -606,7 +606,10 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat // next time and we'll do this over and over... // Can still work with software transform. - GenerateVertexShaderHLSL(VSID, codeBuffer_); + uint32_t attrMask; + uint64_t uniformMask; + bool success = GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &attrMask, &uniformMask, &genErrorString); + _assert_(success); vs = new VSShader(device_, VSID, codeBuffer_, false); } @@ -620,7 +623,11 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat PSShader *fs; if (fsIter == fsCache_.end()) { // Fragment shader not in cache. Let's compile it. - GenerateFragmentShaderHLSL(FSID, codeBuffer_); + std::string errorString; + uint64_t uniformMask; + bool success = GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &uniformMask, &errorString); + // We're supposed to handle all possible cases. + _assert_(success); fs = new PSShader(device_, FSID, codeBuffer_); fsCache_[FSID] = fs; } else { diff --git a/GPU/Directx9/ShaderManagerDX9.h b/GPU/Directx9/ShaderManagerDX9.h index a71b95d2e5..d8062ef881 100644 --- a/GPU/Directx9/ShaderManagerDX9.h +++ b/GPU/Directx9/ShaderManagerDX9.h @@ -21,8 +21,8 @@ #include #include "Common/Common.h" -#include "GPU/Directx9/VertexShaderGeneratorDX9.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" +#include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/ShaderId.h" #include "Common/Math/lin/matrix4x4.h" @@ -98,7 +98,6 @@ private: inline void PSSetFloat(int creg, float value); inline void PSSetFloatArray(int creg, const float *value, int count); - void VSSetMatrix4x3(int creg, const float *m4x3); void VSSetMatrix4x3_3(int creg, const float *m4x3); inline void VSSetColorUniform3(int creg, u32 color); inline void VSSetColorUniform3ExtraFloat(int creg, u32 color, float extra); @@ -118,8 +117,8 @@ private: char *codeBuffer_; - VSShader *lastVShader_; - PSShader *lastPShader_; + VSShader *lastVShader_ = nullptr; + PSShader *lastPShader_ = nullptr; typedef std::map FSCache; FSCache fsCache_; diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index a3ae081396..dfb5c5a754 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -31,7 +31,6 @@ #include "GPU/Directx9/ShaderManagerDX9.h" #include "GPU/Directx9/TextureCacheDX9.h" #include "GPU/Directx9/FramebufferManagerDX9.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" namespace DX9 { @@ -91,7 +90,7 @@ static const D3DSTENCILOP stencilOps[] = { D3DSTENCILOP_KEEP, // reserved }; -inline void DrawEngineDX9::ResetShaderBlending() { +inline void DrawEngineDX9::ResetFramebufferRead() { if (fboTexBound_) { device_->SetTexture(1, nullptr); fboTexBound_ = false; @@ -116,8 +115,8 @@ void DrawEngineDX9::ApplyDrawState(int prim) { if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) { gstate_c.Clean(DIRTY_BLEND_STATE); - // Unfortunately, this isn't implemented yet. - gstate_c.SetAllowShaderBlend(false); + // Unfortunately, this isn't implemented on DX9 yet. + gstate_c.SetAllowFramebufferRead(false); if (gstate.isModeClear()) { dxstate.blend.disable(); @@ -128,19 +127,23 @@ void DrawEngineDX9::ApplyDrawState(int prim) { } else { // Set blend - unless we need to do it in the shader. GenericBlendState blendState; - ConvertBlendState(blendState, gstate_c.allowShaderBlend); + ConvertBlendState(blendState, gstate_c.allowFramebufferRead); - if (blendState.applyShaderBlending) { - if (ApplyShaderBlending()) { - // We may still want to do something about stencil -> alpha. - ApplyStencilReplaceAndLogicOp(blendState.replaceAlphaWithStencil, blendState); + GenericMaskState maskState; + ConvertMaskState(maskState, gstate_c.allowFramebufferRead); + + if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { + if (ApplyFramebufferRead(&fboTexNeedsBind_)) { + // The shader takes over the responsibility for blending, so recompute. + ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); } else { // Until next time, force it off. - ResetShaderBlending(); - gstate_c.SetAllowShaderBlend(false); + ResetFramebufferRead(); + gstate_c.SetAllowFramebufferRead(false); } - } else if (blendState.resetShaderBlending) { - ResetShaderBlending(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); + } else if (blendState.resetFramebufferRead) { + ResetFramebufferRead(); } if (blendState.enabled) { @@ -150,7 +153,7 @@ void DrawEngineDX9::ApplyDrawState(int prim) { dxstate.blendFunc.set( dxBlendFactorLookup[(size_t)blendState.srcColor], dxBlendFactorLookup[(size_t)blendState.dstColor], dxBlendFactorLookup[(size_t)blendState.srcAlpha], dxBlendFactorLookup[(size_t)blendState.dstAlpha]); - if (blendState.dirtyShaderBlend) { + if (blendState.dirtyShaderBlendFixValues) { gstate_c.Dirty(DIRTY_SHADERBLEND); } if (blendState.useBlendColor) { @@ -160,38 +163,7 @@ void DrawEngineDX9::ApplyDrawState(int prim) { dxstate.blend.disable(); } - // PSP color/alpha mask is per bit but we can only support per byte. - // But let's do that, at least. And let's try a threshold. - bool rmask = (gstate.pmskc & 0xFF) < 128; - bool gmask = ((gstate.pmskc >> 8) & 0xFF) < 128; - bool bmask = ((gstate.pmskc >> 16) & 0xFF) < 128; - bool amask = (gstate.pmska & 0xFF) < 128; - -#ifndef MOBILE_DEVICE - u8 abits = (gstate.pmska >> 0) & 0xFF; - u8 rbits = (gstate.pmskc >> 0) & 0xFF; - u8 gbits = (gstate.pmskc >> 8) & 0xFF; - u8 bbits = (gstate.pmskc >> 16) & 0xFF; - if ((rbits != 0 && rbits != 0xFF) || (gbits != 0 && gbits != 0xFF) || (bbits != 0 && bbits != 0xFF)) { - WARN_LOG_REPORT_ONCE(rgbmask, G3D, "Unsupported RGB mask: r=%02x g=%02x b=%02x", rbits, gbits, bbits); - } - if (abits != 0 && abits != 0xFF) { - // The stencil part of the mask is supported. - WARN_LOG_REPORT_ONCE(amask, G3D, "Unsupported alpha/stencil mask: %02x", abits); - } -#endif - - // Let's not write to alpha if stencil isn't enabled. - if (IsStencilTestOutputDisabled()) { - amask = false; - } else { - // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. - if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { - amask = false; - } - } - - dxstate.colorMask.set(rmask, gmask, bmask, amask); + dxstate.colorMask.set(maskState.rgba[0], maskState.rgba[1], maskState.rgba[2], maskState.rgba[3]); } } @@ -298,14 +270,14 @@ void DrawEngineDX9::ApplyDrawStateLate() { if (!gstate.isModeClear()) { textureCache_->ApplyTexture(); - if (fboTexNeedBind_) { + if (fboTexNeedsBind_) { // Note that this is positions, not UVs, that we need the copy from. framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // If we are rendering at a higher resolution, linear is probably best for the dest color. device_->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); device_->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); fboTexBound_ = true; - fboTexNeedBind_ = false; + fboTexNeedsBind_ = false; } // TODO: Test texture? diff --git a/GPU/Directx9/StencilBufferDX9.cpp b/GPU/Directx9/StencilBufferDX9.cpp index d938e4339a..d8e740836b 100644 --- a/GPU/Directx9/StencilBufferDX9.cpp +++ b/GPU/Directx9/StencilBufferDX9.cpp @@ -22,7 +22,6 @@ #include "Core/Reporting.h" #include "GPU/Common/StencilCommon.h" #include "GPU/Directx9/FramebufferManagerDX9.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" #include "GPU/Directx9/ShaderManagerDX9.h" #include "GPU/Directx9/TextureCacheDX9.h" @@ -133,7 +132,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, StencilUploa // TODO: Helper with logging? if (!stencilUploadPS_) { std::string errorMessage; - bool success = CompilePixelShader(device_, stencil_ps, &stencilUploadPS_, NULL, errorMessage); + bool success = CompilePixelShaderD3D9(device_, stencil_ps, &stencilUploadPS_, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); @@ -155,7 +154,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, StencilUploa } if (!stencilUploadVS_) { std::string errorMessage; - bool success = CompileVertexShader(device_, stencil_vs, &stencilUploadVS_, NULL, errorMessage); + bool success = CompileVertexShaderD3D9(device_, stencil_vs, &stencilUploadVS_, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 230bfbf8aa..61ed400351 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -21,8 +21,8 @@ #include "Core/MemMap.h" #include "Core/Reporting.h" #include "GPU/ge_constants.h" + #include "GPU/GPUState.h" -#include "GPU/Directx9/PixelShaderGeneratorDX9.h" #include "GPU/Directx9/TextureCacheDX9.h" #include "GPU/Directx9/FramebufferManagerDX9.h" #include "GPU/Directx9/ShaderManagerDX9.h" @@ -344,7 +344,7 @@ void TextureCacheDX9::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, G const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); LPDIRECT3DTEXTURE9 clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); - Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); shaderManager_->DirtyLastShader(); diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index aa57dcba4b..7f27019bfa 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -66,7 +66,7 @@ private: void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int maxLevel, int scaleFactor, u32 dstFmt); D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); + static TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, u32 dstFmt, int stride, int w, int h); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, FramebufferNotificationChannel channel) override; diff --git a/GPU/Directx9/VertexShaderGeneratorDX9.cpp b/GPU/Directx9/VertexShaderGeneratorDX9.cpp deleted file mode 100644 index 976158ed74..0000000000 --- a/GPU/Directx9/VertexShaderGeneratorDX9.cpp +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include -#include - -#if defined(_WIN32) && defined(_DEBUG) -#include "Common/CommonWindows.h" -#endif - -#include "Common/StringUtils.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" -#include "Core/Config.h" -#include "Core/System.h" - -#include "GPU/Directx9/VertexShaderGeneratorDX9.h" -#include "GPU/Common/VertexDecoderCommon.h" -#include "GPU/Common/ShaderUniforms.h" - -#undef WRITE - -#define WRITE p+=sprintf - -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, - LIGHT_FULL, -}; - -void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage lang) { - char *p = buffer; - - bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH); - bool lmode = id.Bit(VS_BIT_LMODE); - bool doTexture = id.Bit(VS_BIT_DO_TEXTURE); - bool doTextureTransform = id.Bit(VS_BIT_DO_TEXTURE_TRANSFORM); - - GETexMapMode uvGenMode = static_cast(id.Bits(VS_BIT_UVGEN_MODE, 2)); - - // this is only valid for some settings of uvGenMode - GETexProjMapMode uvProjMode = static_cast(id.Bits(VS_BIT_UVPROJ_MODE, 2)); - bool doShadeMapping = uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP; - bool doFlatShading = id.Bit(VS_BIT_FLATSHADE); - - bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); - bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; - bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform; - bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform; - bool enableFog = id.Bit(VS_BIT_ENABLE_FOG); - 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); - - bool doBezier = id.Bit(VS_BIT_BEZIER); - bool doSpline = id.Bit(VS_BIT_SPLINE); - bool hasColorTess = id.Bit(VS_BIT_HAS_COLOR_TESS); - bool hasTexcoordTess = id.Bit(VS_BIT_HAS_TEXCOORD_TESS); - bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS); - bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS); - - DoLightComputation doLight[4] = { LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF }; - if (useHWTransform) { - int shadeLight0 = doShadeMapping ? ls0 : -1; - int shadeLight1 = doShadeMapping ? ls1 : -1; - for (int i = 0; i < 4; i++) { - if (i == shadeLight0 || i == shadeLight1) - doLight[i] = LIGHT_SHADE; - if (enableLighting && id.Bit(VS_BIT_LIGHT0_ENABLE + i)) - doLight[i] = LIGHT_FULL; - } - } - - 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) { - WRITE(p, "float4x4 u_proj_through : register(c%i);\n", CONST_VS_PROJ_THROUGH); - } else { - WRITE(p, "float4x4 u_proj : register(c%i);\n", CONST_VS_PROJ); - // Add all the uniforms we'll need to transform properly. - } - - if (enableFog) { - WRITE(p, "float2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF); - } - if (useHWTransform || !hasColor) - WRITE(p, "float4 u_matambientalpha : register(c%i);\n", CONST_VS_MATAMBIENTALPHA); // matambient + matalpha - - if (useHWTransform) { - // When transforming by hardware, we need a great deal more uniforms... - WRITE(p, "float4x3 u_world : register(c%i);\n", CONST_VS_WORLD); - 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); - } - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_OFF) { - // This is needed for shade mapping - WRITE(p, "float3 u_lightpos%i : register(c%i);\n", i, CONST_VS_LIGHTPOS + i); - } - if (doLight[i] == LIGHT_FULL) { - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - - if (type != GE_LIGHTTYPE_DIRECTIONAL) - WRITE(p, "float3 u_lightatt%i : register(c%i);\n", i, CONST_VS_LIGHTATT + i); - - if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { - WRITE(p, "float3 u_lightdir%i : register(c%i);\n", i, CONST_VS_LIGHTDIR + i); - WRITE(p, "float4 u_lightangle_spotCoef%i : register(c%i);\n", i, CONST_VS_LIGHTANGLE_SPOTCOEF + i); - } - WRITE(p, "float3 u_lightambient%i : register(c%i);\n", i, CONST_VS_LIGHTAMBIENT + i); - WRITE(p, "float3 u_lightdiffuse%i : register(c%i);\n", i, CONST_VS_LIGHTDIFFUSE + i); - - if (comp == GE_LIGHTCOMP_BOTH) { - WRITE(p, "float3 u_lightspecular%i : register(c%i);\n", i, CONST_VS_LIGHTSPECULAR + i); - } - } - } - if (enableLighting) { - WRITE(p, "float4 u_ambient : register(c%i);\n", CONST_VS_AMBIENT); - if ((matUpdate & 2) == 0 || !hasColor) - WRITE(p, "float3 u_matdiffuse : register(c%i);\n", CONST_VS_MATDIFFUSE); - // if ((matUpdate & 4) == 0) - WRITE(p, "float4 u_matspecular : register(c%i);\n", CONST_VS_MATSPECULAR); // Specular coef is contained in alpha - WRITE(p, "float3 u_matemissive : register(c%i);\n", CONST_VS_MATEMISSIVE); - } - } - - if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, "float4 u_depthRange : register(c%i);\n", CONST_VS_DEPTHRANGE); - } - if (!isModeThrough) { - WRITE(p, "float4 u_cullRangeMin : register(c%i);\n", CONST_VS_CULLRANGEMIN); - WRITE(p, "float4 u_cullRangeMax : register(c%i);\n", CONST_VS_CULLRANGEMAX); - } - } 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); - } - - bool scaleUV = !isModeThrough && (uvGenMode == GE_TEXMAP_TEXTURE_COORDS || uvGenMode == GE_TEXMAP_UNKNOWN); - - // And the "varyings". - bool texCoordInVec3 = false; - if (useHWTransform) { - WRITE(p, "struct VS_IN { \n"); - 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"); - } - if (hasColor) { - WRITE(p, " float4 color0 : COLOR0;\n"); - } - if (hasNormal) { - WRITE(p, " float3 normal : NORMAL;\n"); - } - WRITE(p, " float3 position : POSITION;\n"); - WRITE(p, "};\n"); - - } else { - WRITE(p, "struct VS_IN {\n"); - WRITE(p, " float4 position : POSITION;\n"); - if (doTexture && hasTexcoord) { - if (doTextureTransform && !isModeThrough) { - texCoordInVec3 = true; - WRITE(p, " float3 texcoord : TEXCOORD0;\n"); - } - else - WRITE(p, " float2 texcoord : TEXCOORD0;\n"); - } - if (hasColor) { - WRITE(p, " float4 color0 : COLOR0;\n"); - } - // only software transform supplies color1 as vertex data - if (lmode) { - WRITE(p, " float4 color1 : COLOR1;\n"); - } - WRITE(p, "};\n"); - } - - WRITE(p, "struct VS_OUT {\n"); - if (doTexture) { - WRITE(p, " float3 v_texcoord : TEXCOORD0;\n"); - } - const char *colorInterpolation = doFlatShading && lang == HLSL_D3D11 ? "nointerpolation " : ""; - WRITE(p, " %sfloat4 v_color0 : COLOR0;\n", colorInterpolation); - if (lmode) - WRITE(p, " float3 v_color1 : COLOR1;\n"); - - if (enableFog) { - WRITE(p, " float v_fogdepth: TEXCOORD1;\n"); - } - if (lang == HLSL_DX9) { - WRITE(p, " float4 gl_Position : POSITION;\n"); - } else { - WRITE(p, " float4 gl_Position : SV_Position;\n"); - } - WRITE(p, "};\n"); - - // Confirmed: Through mode gets through exactly the same in GL and D3D in Phantasy Star: Text is 38023.0 in the test scene. - - if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. - // The Z range in D3D is different but we compensate for that using parameters. - WRITE(p, "\nfloat4 depthRoundZVP(float4 v) {\n"); - WRITE(p, " float z = v.z / v.w;\n"); - WRITE(p, " z = (z * u_depthRange.x + u_depthRange.y);\n"); - WRITE(p, " z = floor(z);\n"); - WRITE(p, " z = (z - u_depthRange.z) * u_depthRange.w;\n"); - if (PSP_CoreParameter().compat.flags().DecreaseVertexDepthAccuracy) { - WRITE(p, " z /= 1.000031;\n"); // Hokuto no Ken: Raoh Gaiden - Ten no Haoh - } - WRITE(p, " return float4(v.x, v.y, z * v.w, v.w);\n"); - WRITE(p, "}\n\n"); - } - - // Hardware tessellation - if (doSpline || doBezier) { - if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - WRITE(p, "struct TessData {\n"); - WRITE(p, " float3 pos; float pad1;\n"); - WRITE(p, " float2 tex; float2 pad2;\n"); - WRITE(p, " float4 col;\n"); - WRITE(p, "};\n"); - WRITE(p, "StructuredBuffer tess_data : register(t0);\n"); - - WRITE(p, "struct TessWeight {\n"); - WRITE(p, " float4 basis;\n"); - WRITE(p, " float4 deriv;\n"); - WRITE(p, "};\n"); - WRITE(p, "StructuredBuffer tess_weights_u : register(t1);\n"); - WRITE(p, "StructuredBuffer tess_weights_v : register(t2);\n"); - } - - const char *init[3] = { "0.0, 0.0", "0.0, 0.0, 0.0", "0.0, 0.0, 0.0, 0.0" }; - for (int i = 2; i <= 4; i++) { - // Define 3 types float2, float3, float4 - WRITE(p, "float%d tess_sample(in float%d points[16], float4x4 weights) {\n", i, i); - WRITE(p, " float%d pos = float%d(%s);\n", i, i, init[i - 2]); - for (int v = 0; v < 4; ++v) { - for (int u = 0; u < 4; ++u) { - WRITE(p, " pos += weights[%i][%i] * points[%i];\n", v, u, v * 4 + u); - } - } - WRITE(p, " return pos;\n"); - WRITE(p, "}\n"); - } - - WRITE(p, "float4x4 outerProduct(float4 u, float4 v) {\n"); - WRITE(p, " return mul((float4x1)v, (float1x4)u);\n"); - WRITE(p, "}\n"); - - WRITE(p, "struct Tess {\n"); - WRITE(p, " float3 pos;\n"); - if (doTexture) - WRITE(p, " float2 tex;\n"); - WRITE(p, " float4 col;\n"); - if (hasNormalTess) - WRITE(p, " float3 nrm;\n"); - WRITE(p, "};\n"); - - WRITE(p, "void tessellate(in VS_IN In, out Tess tess) {\n"); - WRITE(p, " int2 point_pos = int2(In.position.z, In.normal.z)%s;\n", doBezier ? " * 3" : ""); - WRITE(p, " int2 weight_idx = int2(In.position.xy);\n"); - // Load 4x4 control points - WRITE(p, " float3 _pos[16];\n"); - WRITE(p, " float2 _tex[16];\n"); - WRITE(p, " float4 _col[16];\n"); - WRITE(p, " int index;\n"); - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - WRITE(p, " index = (%i + point_pos.y) * u_spline_counts + (%i + point_pos.x);\n", i, j); - WRITE(p, " _pos[%i] = tess_data[index].pos;\n", i * 4 + j); - if (doTexture && hasTexcoordTess) - WRITE(p, " _tex[%i] = tess_data[index].tex;\n", i * 4 + j); - if (hasColorTess) - WRITE(p, " _col[%i] = tess_data[index].col;\n", i * 4 + j); - } - } - - // Basis polynomials as weight coefficients - WRITE(p, " float4 basis_u = tess_weights_u[weight_idx.x].basis;\n"); - WRITE(p, " float4 basis_v = tess_weights_v[weight_idx.y].basis;\n"); - WRITE(p, " float4x4 basis = outerProduct(basis_u, basis_v);\n"); - - // Tessellate - WRITE(p, " tess.pos = tess_sample(_pos, basis);\n"); - if (doTexture) { - if (hasTexcoordTess) - WRITE(p, " tess.tex = tess_sample(_tex, basis);\n"); - else - WRITE(p, " tess.tex = In.normal.xy;\n"); - } - if (hasColorTess) - WRITE(p, " tess.col = tess_sample(_col, basis);\n"); - else - WRITE(p, " tess.col = u_matambientalpha;\n"); - if (hasNormalTess) { - // Derivatives as weight coefficients - WRITE(p, " float4 deriv_u = tess_weights_u[weight_idx.x].deriv;\n"); - WRITE(p, " float4 deriv_v = tess_weights_v[weight_idx.y].deriv;\n"); - - WRITE(p, " float3 du = tess_sample(_pos, outerProduct(deriv_u, basis_v));\n"); - WRITE(p, " float3 dv = tess_sample(_pos, outerProduct(basis_u, deriv_v));\n"); - WRITE(p, " tess.nrm = normalize(cross(du, dv));\n"); - } - WRITE(p, "}\n"); - } - - WRITE(p, "VS_OUT main(VS_IN In) {\n"); - WRITE(p, " VS_OUT Out;\n"); - if (!useHWTransform) { - // Simple pass-through of vertex data to fragment shader - if (doTexture) { - if (texCoordInVec3) { - WRITE(p, " Out.v_texcoord = In.texcoord;\n"); - } else { - WRITE(p, " Out.v_texcoord = float3(In.texcoord, 1.0);\n"); - } - } - if (hasColor) { - WRITE(p, " Out.v_color0 = In.color0;\n"); - if (lmode) - WRITE(p, " Out.v_color1 = In.color1.rgb;\n"); - } else { - WRITE(p, " Out.v_color0 = In.u_matambientalpha;\n"); - if (lmode) - WRITE(p, " Out.v_color1 = float3(0.0);\n"); - } - if (enableFog) { - WRITE(p, " Out.v_fogdepth = In.position.w;\n"); - } - if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - if (isModeThrough) { - WRITE(p, " float4 outPos = mul(u_proj_through, float4(In.position.xyz, 1.0));\n"); - } else { - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " float4 outPos = depthRoundZVP(mul(u_proj, float4(In.position.xyz, 1.0)));\n"); - } else { - WRITE(p, " float4 outPos = mul(u_proj, float4(In.position.xyz, 1.0));\n"); - } - } - } else { - if (isModeThrough) { - WRITE(p, " float4 outPos = mul(float4(In.position.xyz, 1.0), u_proj_through);\n"); - } else { - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " float4 outPos = depthRoundZVP(mul(float4(In.position.xyz, 1.0), u_proj));\n"); - } else { - WRITE(p, " float4 outPos = mul(float4(In.position.xyz, 1.0), u_proj);\n"); - } - } - } - } else { - // Step 1: World Transform / Skinning - if (!enableBones) { - if (doSpline || doBezier) { - // Hardware tessellation - WRITE(p, " Tess tess;\n"); - WRITE(p, " tessellate(In, tess);\n"); - - WRITE(p, " float3 worldpos = mul(float4(tess.pos.xyz, 1.0), u_world);\n"); - if (hasNormalTess) - WRITE(p, " float3 worldnormal = normalize(mul(float4(%stess.nrm, 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"); - } - } else { - 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"); - - if (lang == HLSL_D3D11 || lang == HLSL_D3D11_LEVEL9) { - // Final view and projection transforms. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " float4 outPos = depthRoundZVP(mul(u_proj, viewPos));\n"); - } else { - WRITE(p, " float4 outPos = mul(u_proj, viewPos);\n"); - } - } else { - // Final view and projection transforms. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " float4 outPos = depthRoundZVP(mul(viewPos, u_proj));\n"); - } else { - WRITE(p, " float4 outPos = mul(viewPos, u_proj);\n"); - } - } - - // TODO: Declare variables for dots for shade mapping if needed. - - const char *ambientStr = (matUpdate & 1) && hasColor ? "In.color0" : "u_matambientalpha"; - const char *diffuseStr = (matUpdate & 2) && hasColor ? "In.color0.rgb" : "u_matdiffuse"; - const char *specularStr = (matUpdate & 4) && hasColor ? "In.color0.rgb" : "u_matspecular.rgb"; - if (doBezier || doSpline) { - // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. - ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "u_matambientalpha"; - diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "u_matdiffuse"; - specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "u_matspecular.rgb"; - } - - bool diffuseIsZero = true; - bool specularIsZero = true; - bool distanceNeeded = false; - bool anySpots = false; - if (enableLighting) { - WRITE(p, " float4 lightSum0 = u_ambient * %s + float4(u_matemissive, 0.0);\n", ambientStr); - - for (int i = 0; i < 4; i++) { - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - if (doLight[i] != LIGHT_FULL) - continue; - diffuseIsZero = false; - if (comp == GE_LIGHTCOMP_BOTH) - specularIsZero = false; - if (type != GE_LIGHTTYPE_DIRECTIONAL) - distanceNeeded = true; - if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) - anySpots = true; - } - - if (!specularIsZero) { - WRITE(p, " float3 lightSum1 = 0;\n"); - } - if (!diffuseIsZero) { - WRITE(p, " float3 toLight;\n"); - WRITE(p, " float3 diffuse;\n"); - } - if (distanceNeeded) { - WRITE(p, " float distance;\n"); - WRITE(p, " float lightScale;\n"); - } - WRITE(p, " float ldot;\n"); - if (anySpots) { - WRITE(p, " float angle;\n"); - } - } - - // Calculate lights if needed. If shade mapping is enabled, lights may need to be - // at least partially calculated. - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_FULL) - continue; - - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - - if (type == GE_LIGHTTYPE_DIRECTIONAL) { - // We prenormalize light positions for directional lights. - WRITE(p, " toLight = u_lightpos%i;\n", i); - } else { - WRITE(p, " toLight = u_lightpos%i - worldpos;\n", i); - WRITE(p, " distance = length(toLight);\n"); - WRITE(p, " toLight /= distance;\n"); - } - - bool doSpecular = comp == GE_LIGHTCOMP_BOTH; - bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; - - WRITE(p, " ldot = dot(toLight, worldnormal);\n"); - if (poweredDiffuse) { - // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. - // Seen in Tales of the World: Radiant Mythology (#2424.) - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); - } - - const char *timesLightScale = " * lightScale"; - - // Attenuation - switch (type) { - case GE_LIGHTTYPE_DIRECTIONAL: - timesLightScale = ""; - break; - case GE_LIGHTTYPE_POINT: - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, float3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); - break; - case GE_LIGHTTYPE_SPOT: - case GE_LIGHTTYPE_UNKNOWN: - WRITE(p, " angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); - WRITE(p, " if (angle >= u_lightangle_spotCoef%i.x) {\n", i); - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, float3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); - WRITE(p, " } else {\n"); - WRITE(p, " lightScale = 0.0;\n"); - WRITE(p, " }\n"); - break; - default: - // ILLEGAL - break; - } - - WRITE(p, " diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); - if (doSpecular) { - WRITE(p, " if (ldot >= 0.0) {\n"); - WRITE(p, " ldot = dot(normalize(toLight + float3(0.0, 0.0, 1.0)), worldnormal);\n"); - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); - WRITE(p, " if (ldot > 0.0)\n"); - WRITE(p, " lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); - WRITE(p, " }\n"); - } - WRITE(p, " lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); - } - - if (enableLighting) { - // Sum up ambient, emissive here. - if (lmode) { - WRITE(p, " Out.v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - // v_color1 only exists when lmode = 1. - if (specularIsZero) { - WRITE(p, " Out.v_color1 = float3(0, 0, 0);\n"); - } else { - WRITE(p, " Out.v_color1 = clamp(lightSum1, 0.0, 1.0);\n"); - } - } else { - if (specularIsZero) { - WRITE(p, " Out.v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - } else { - WRITE(p, " Out.v_color0 = clamp(clamp(lightSum0, 0.0, 1.0) + float4(lightSum1, 0.0), 0.0, 1.0);\n"); - } - } - } else { - // Lighting doesn't affect color. - if (hasColor) { - if (doBezier || doSpline) - WRITE(p, " Out.v_color0 = tess.col;\n"); - else - WRITE(p, " Out.v_color0 = In.color0;\n"); - } else { - WRITE(p, " Out.v_color0 = u_matambientalpha;\n"); - } - if (lmode) - WRITE(p, " Out.v_color1 = float3(0, 0, 0);\n"); - } - - // Step 3: UV generation - if (doTexture) { - switch (uvGenMode) { - case GE_TEXMAP_TEXTURE_COORDS: // Scale-offset. Easy. - case GE_TEXMAP_UNKNOWN: // Not sure what this is, but Riviera uses it. Treating as coords works. - if (scaleUV) { - if (hasTexcoord) { - if (doBezier || doSpline) - WRITE(p, " Out.v_texcoord = float3(tess.tex.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n"); - else - WRITE(p, " Out.v_texcoord = float3(In.texcoord.xy * u_uvscaleoffset.xy, 0.0);\n"); - } else { - WRITE(p, " Out.v_texcoord = float3(0.0, 0.0, 0.0);\n"); - } - } else { - if (hasTexcoord) { - WRITE(p, " Out.v_texcoord = float3(In.texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n"); - } else { - WRITE(p, " Out.v_texcoord = float3(u_uvscaleoffset.zw, 0.0);\n"); - } - } - break; - - case GE_TEXMAP_TEXTURE_MATRIX: // Projection mapping. - { - std::string temp_tc; - switch (uvProjMode) { - case GE_PROJMAP_POSITION: // Use model space XYZ as source - temp_tc = "float4(In.position.xyz, 1.0)"; - break; - case GE_PROJMAP_UV: // Use unscaled UV as source - { - if (hasTexcoord) { - temp_tc = StringFromFormat("float4(In.texcoord.xy, 0.0, 1.0)"); - } else { - temp_tc = "float4(0.0, 0.0, 0.0, 1.0)"; - } - } - break; - case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "float4(normalize(-In.normal), 1.0)" : "float4(normalize(In.normal), 1.0)"; - else - temp_tc = "float4(0.0, 0.0, 1.0, 1.0)"; - break; - case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "float4(-In.normal, 1.0)" : "float4(In.normal, 1.0)"; - else - temp_tc = "float4(0.0, 0.0, 1.0, 1.0)"; - break; - } - // Transform by texture matrix. XYZ as we are doing projection mapping. - WRITE(p, " Out.v_texcoord.xyz = mul(%s, u_tex) * float3(u_uvscaleoffset.xy, 1.0);\n", temp_tc.c_str()); - } - break; - - case GE_TEXMAP_ENVIRONMENT_MAP: // Shade mapping - use dots from light sources. - { - std::string lightFactor0 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls0, ls0); - std::string lightFactor1 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls1, ls1); - WRITE(p, " Out.v_texcoord = float3(u_uvscaleoffset.xy * float2(1.0 + %s, 1.0 + %s) * 0.5, 1.0);\n", lightFactor0.c_str(), lightFactor1.c_str()); - } - break; - - default: - // ILLEGAL - break; - } - } - - // Compute fogdepth - if (enableFog) { - WRITE(p, " Out.v_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n"); - } - } - - if (!isModeThrough && gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING)) { - WRITE(p, " float3 projPos = outPos.xyz / outPos.w;\n"); - // Vertex range culling doesn't happen when depth is clamped, so only do this if in range. - WRITE(p, " if (u_cullRangeMin.w <= 0.0 || (projPos.z >= u_cullRangeMin.z && projPos.z <= u_cullRangeMax.z)) {\n"); - const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y || projPos.z < u_cullRangeMin.z"; - const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y || projPos.z > u_cullRangeMax.z"; - WRITE(p, " if (%s || %s) {\n", outMin, outMax); - WRITE(p, " outPos.w = u_cullRangeMax.w;\n"); - WRITE(p, " }\n"); - WRITE(p, " }\n"); - } - WRITE(p, " Out.gl_Position = outPos;\n"); - - WRITE(p, " return Out;\n"); - WRITE(p, "}\n"); -} - -}; diff --git a/GPU/Directx9/VertexShaderGeneratorDX9.h b/GPU/Directx9/VertexShaderGeneratorDX9.h deleted file mode 100644 index aecc113ea3..0000000000 --- a/GPU/Directx9/VertexShaderGeneratorDX9.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2012- 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/. - -#pragma once - -#include "GPU/Common/ShaderID.h" - -namespace DX9 { - - void GenerateVertexShaderHLSL(const VShaderID &id, char *buffer, ShaderLanguage lang = HLSL_DX9); - - enum { - CONST_VS_PROJ = 0, - CONST_VS_PROJ_THROUGH = 4, - CONST_VS_VIEW = 8, - CONST_VS_WORLD = 11, - CONST_VS_TEXMTX = 14, - CONST_VS_UVSCALEOFFSET = 17, - CONST_VS_FOGCOEF = 18, - CONST_VS_AMBIENT = 19, - CONST_VS_MATAMBIENTALPHA = 20, - CONST_VS_MATDIFFUSE = 21, - CONST_VS_MATSPECULAR = 22, - CONST_VS_MATEMISSIVE = 23, - CONST_VS_LIGHTPOS = 24, - CONST_VS_LIGHTDIR = 28, - CONST_VS_LIGHTATT = 32, - CONST_VS_LIGHTANGLE_SPOTCOEF = 36, - CONST_VS_LIGHTDIFFUSE = 40, - 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, - CONST_VS_CULLRANGEMIN = 80, - CONST_VS_CULLRANGEMAX = 81, - }; - -}; diff --git a/GPU/GLES/DepalettizeShaderGLES.cpp b/GPU/GLES/DepalettizeShaderGLES.cpp index 59517c0f4d..47ddfa1907 100644 --- a/GPU/GLES/DepalettizeShaderGLES.cpp +++ b/GPU/GLES/DepalettizeShaderGLES.cpp @@ -98,7 +98,7 @@ GLRTexture *DepalShaderCacheGLES::GetClutTexture(GEPaletteFormat clutFormat, con int texturePixels = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 256 : 512; DepalTexture *tex = new DepalTexture(); - tex->texture = render_->CreateTexture(GL_TEXTURE_2D); + tex->texture = render_->CreateTexture(GL_TEXTURE_2D, texturePixels, 1, 1); uint8_t *clutCopy = new uint8_t[1024]; memcpy(clutCopy, rawClut, 1024); @@ -162,7 +162,7 @@ DepalShader *DepalShaderCacheGLES::GetDepalettizeShader(uint32_t clutMode, GEBuf char *buffer = new char[2048]; - GenerateDepalShader(buffer, pixelFormat, useGL3_ ? GLSL_300 : GLSL_140); + GenerateDepalShader(buffer, pixelFormat, useGL3_ ? GLSL_3xx : GLSL_1xx); std::string src(buffer); GLRShader *fragShader = render_->CreateShader(GL_FRAGMENT_SHADER, src, "depal"); diff --git a/GPU/GLES/DepthBufferGLES.cpp b/GPU/GLES/DepthBufferGLES.cpp index fa3ca00c5c..9cd487ae5a 100644 --- a/GPU/GLES/DepthBufferGLES.cpp +++ b/GPU/GLES/DepthBufferGLES.cpp @@ -126,7 +126,7 @@ void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int } shaderManagerGL_->DirtyLastShader(); - auto *blitFBO = GetTempFBO(TempFBO::COPY, vfb->renderWidth, vfb->renderHeight, Draw::FBO_8888); + auto *blitFBO = GetTempFBO(TempFBO::COPY, vfb->renderWidth, vfb->renderHeight); draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "PackDepthbuffer"); render_->SetViewport({ 0, 0, (float)vfb->renderWidth, (float)vfb->renderHeight, 0.0f, 1.0f }); diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index deb288bd85..1873156806 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -696,7 +696,7 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p prevSizeU = size_u; prevSizeV = size_v; if (!data_tex[0]) - data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D); + data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D, size_u * 3, size_v, 1); renderManager_->TextureImage(data_tex[0], 0, size_u * 3, size_v, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[0], 0, false); } @@ -714,7 +714,7 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p if (prevSizeWU < weights.size_u) { prevSizeWU = weights.size_u; if (!data_tex[1]) - data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D); + data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_u * 2, 1, 1); renderManager_->TextureImage(data_tex[1], 0, weights.size_u * 2, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[1], 0, false); } @@ -725,7 +725,7 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p if (prevSizeWV < weights.size_v) { prevSizeWV = weights.size_v; if (!data_tex[2]) - data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D); + data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_v * 2, 1, 1); renderManager_->TextureImage(data_tex[2], 0, weights.size_v * 2, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false); renderManager_->FinalizeTexture(data_tex[2], 0, false); } diff --git a/GPU/GLES/DrawEngineGLES.h b/GPU/GLES/DrawEngineGLES.h index 9291543478..fd694f4d7e 100644 --- a/GPU/GLES/DrawEngineGLES.h +++ b/GPU/GLES/DrawEngineGLES.h @@ -27,7 +27,7 @@ #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/GPUStateUtils.h" -#include "GPU/GLES/FragmentShaderGeneratorGLES.h" +#include "GPU/Common/FragmentShaderGenerator.h" class LinkedShader; class ShaderManagerGLES; @@ -193,7 +193,7 @@ private: void DoFlush(); void ApplyDrawState(int prim); void ApplyDrawStateLate(bool setStencil, int stencilValue); - void ResetShaderBlending(); + void ResetFramebufferRead(); GLRInputLayout *SetupDecFmtForDraw(LinkedShader *program, const DecVtxFormat &decFmt); diff --git a/GPU/GLES/FragmentShaderGeneratorGLES.cpp b/GPU/GLES/FragmentShaderGeneratorGLES.cpp deleted file mode 100644 index 77174224db..0000000000 --- a/GPU/GLES/FragmentShaderGeneratorGLES.cpp +++ /dev/null @@ -1,826 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include -#include - -#include "Common/Log.h" -#include "Common/StringUtils.h" -#include "Common/GPU/OpenGL/GLFeatures.h" -#include "Core/Reporting.h" -#include "Core/Config.h" -#include "Core/System.h" -#include "GPU/Common/GPUStateUtils.h" -#include "GPU/Common/ShaderId.h" -#include "GPU/GLES/FragmentShaderGeneratorGLES.h" -#include "GPU/GLES/FramebufferManagerGLES.h" -#include "GPU/GLES/ShaderManagerGLES.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" - -#define WRITE p+=sprintf - -// #define DEBUG_SHADER - -// Missing: Z depth range -bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniformMask) { - char *p = buffer; - - *uniformMask = 0; - // In GLSL ES 3.0, you use "in" variables instead of varying. - - bool glslES30 = false; - const char *varying = "varying"; - const char *fragColor0 = "gl_FragColor"; - const char *fragColor1 = "fragColor1"; - const char *texture = "texture2D"; - const char *texelFetch = NULL; - bool highpFog = false; - bool highpTexcoord = false; - bool bitwiseOps = false; - const char *lastFragData = nullptr; - - ReplaceAlphaType stencilToAlpha = static_cast(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)); - - if (gl_extensions.IsGLES) { - // ES doesn't support dual source alpha :( - if (gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300)) { - WRITE(p, "#version 300 es\n"); // GLSL ES 3.0 - fragColor0 = "fragColor0"; - texture = "texture"; - glslES30 = true; - bitwiseOps = true; - texelFetch = "texelFetch"; - - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE && gl_extensions.EXT_blend_func_extended) { - WRITE(p, "#extension GL_EXT_blend_func_extended : require\n"); - } - } else { - WRITE(p, "#version 100\n"); // GLSL ES 1.0 - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - bitwiseOps = true; - texelFetch = "texelFetch2D"; - } - if (gl_extensions.EXT_blend_func_extended) { - // Oldy moldy GLES, so use the fixed output name. - fragColor1 = "gl_SecondaryFragColorEXT"; - - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE && gl_extensions.EXT_blend_func_extended) { - WRITE(p, "#extension GL_EXT_blend_func_extended : require\n"); - } - } - } - - // PowerVR needs highp to do the fog in MHU correctly. - // Others don't, and some can't handle highp in the fragment shader. - highpFog = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? true : false; - highpTexcoord = highpFog; - - if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { - if (gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300) && gl_extensions.EXT_shader_framebuffer_fetch) { - WRITE(p, "#extension GL_EXT_shader_framebuffer_fetch : require\n"); - lastFragData = "fragColor0"; - } else if (gl_extensions.EXT_shader_framebuffer_fetch) { - WRITE(p, "#extension GL_EXT_shader_framebuffer_fetch : require\n"); - lastFragData = "gl_LastFragData[0]"; - } else if (gl_extensions.NV_shader_framebuffer_fetch) { - // GL_NV_shader_framebuffer_fetch is available on mobile platform and ES 2.0 only but not on desktop. - WRITE(p, "#extension GL_NV_shader_framebuffer_fetch : require\n"); - lastFragData = "gl_LastFragData[0]"; - } else if (gl_extensions.ARM_shader_framebuffer_fetch) { - WRITE(p, "#extension GL_ARM_shader_framebuffer_fetch : require\n"); - lastFragData = "gl_LastFragColorARM"; - } - } - - WRITE(p, "precision lowp float;\n"); - } else { - if (!gl_extensions.ForceGL2 || gl_extensions.IsCoreContext) { - if (gl_extensions.VersionGEThan(3, 3, 0)) { - fragColor0 = "fragColor0"; - texture = "texture"; - glslES30 = true; - bitwiseOps = true; - texelFetch = "texelFetch"; - WRITE(p, "#version 330\n"); - } else if (gl_extensions.VersionGEThan(3, 0, 0)) { - fragColor0 = "fragColor0"; - bitwiseOps = true; - texelFetch = "texelFetch"; - WRITE(p, "#version 130\n"); - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - } - } else { - WRITE(p, "#version 110\n"); - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - bitwiseOps = true; - texelFetch = "texelFetch2D"; - } - } - } - - // We remove these everywhere - GL4, GL3, Mac-forced-GL2, etc. - WRITE(p, "#define lowp\n"); - WRITE(p, "#define mediump\n"); - WRITE(p, "#define highp\n"); - } - - if (glslES30 || gl_extensions.IsCoreContext) { - varying = "in"; - } - - bool lmode = id.Bit(FS_BIT_LMODE); - bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); - bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); - bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); - - bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); - bool testForceToZero = id.Bit(FS_BIT_TEST_DISCARD_TO_ZERO); - bool enableColorTest = id.Bit(FS_BIT_COLOR_TEST); - bool colorTestAgainstZero = id.Bit(FS_BIT_COLOR_AGAINST_ZERO); - bool enableColorDoubling = id.Bit(FS_BIT_COLOR_DOUBLE); - 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); - bool needShaderTexClamp = id.Bit(FS_BIT_SHADER_TEX_CLAMP); - - GETexFunc texFunc = (GETexFunc)id.Bits(FS_BIT_TEXFUNC, 3); - bool textureAtOffset = id.Bit(FS_BIT_TEXTURE_AT_OFFSET); - - ReplaceBlendType replaceBlend = static_cast(id.Bits(FS_BIT_REPLACE_BLEND, 3)); - - GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); - GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); - GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); - - bool isModeClear = id.Bit(FS_BIT_CLEARMODE); - - if (shaderDepal && gl_extensions.IsGLES) { - WRITE(p, "precision highp int;\n"); - } - - const char *shading = ""; - if (glslES30) - shading = doFlatShading ? "flat" : ""; - - if (doTexture) - WRITE(p, "uniform sampler2D tex;\n"); - - if (!isModeClear && replaceBlend > REPLACE_BLEND_STANDARD) { - *uniformMask |= DIRTY_SHADERBLEND; - if (!gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH) && replaceBlend == REPLACE_BLEND_COPY_FBO) { - if (!texelFetch) { - WRITE(p, "uniform vec2 u_fbotexSize;\n"); - } - WRITE(p, "uniform sampler2D fbotex;\n"); - } - if (replaceBlendFuncA >= GE_SRCBLEND_FIXA) { - WRITE(p, "uniform vec3 u_blendFixA;\n"); - } - if (replaceBlendFuncB >= GE_DSTBLEND_FIXB) { - WRITE(p, "uniform vec3 u_blendFixB;\n"); - } - } - - if (needShaderTexClamp && doTexture) { - *uniformMask |= DIRTY_TEXCLAMP; - WRITE(p, "uniform vec4 u_texclamp;\n"); - if (id.Bit(FS_BIT_TEXTURE_AT_OFFSET)) { - WRITE(p, "uniform vec2 u_texclampoff;\n"); - } - } - - if (enableAlphaTest || enableColorTest) { - if (g_Config.bFragmentTestCache) { - WRITE(p, "uniform sampler2D testtex;\n"); - } else { - *uniformMask |= DIRTY_ALPHACOLORREF; - WRITE(p, "uniform vec4 u_alphacolorref;\n"); - if (bitwiseOps && ((enableColorTest && !colorTestAgainstZero) || (enableAlphaTest && !alphaTestAgainstZero))) { - *uniformMask |= DIRTY_ALPHACOLORMASK; - WRITE(p, "uniform ivec4 u_alphacolormask;\n"); - } - } - } - - 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; - WRITE(p, "uniform float u_stencilReplaceValue;\n"); - } - if (doTexture && texFunc == GE_TEXFUNC_BLEND) { - *uniformMask |= DIRTY_TEXENV; - WRITE(p, "uniform vec3 u_texenv;\n"); - } - - WRITE(p, "%s %s vec4 v_color0;\n", shading, varying); - if (lmode) - WRITE(p, "%s %s vec3 v_color1;\n", shading, varying); - if (enableFog) { - *uniformMask |= DIRTY_FOGCOLOR; - WRITE(p, "uniform vec3 u_fogcolor;\n"); - WRITE(p, "%s %s float v_fogdepth;\n", varying, highpFog ? "highp" : "mediump"); - } - if (doTexture) { - WRITE(p, "%s %s vec3 v_texcoord;\n", varying, highpTexcoord ? "highp" : "mediump"); - } - - if (!g_Config.bFragmentTestCache) { - if (enableAlphaTest && !alphaTestAgainstZero) { - if (bitwiseOps) { - WRITE(p, "int roundAndScaleTo255i(in float x) { return int(floor(x * 255.0 + 0.5)); }\n"); - } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { - WRITE(p, "float roundTo255thf(in mediump float x) { mediump float y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); - } else { - WRITE(p, "float roundAndScaleTo255f(in float x) { return floor(x * 255.0 + 0.5); }\n"); - } - } - if (enableColorTest && !colorTestAgainstZero) { - if (bitwiseOps) { - WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); - } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { - WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n"); - } else { - WRITE(p, "vec3 roundAndScaleTo255v(in vec3 x) { return floor(x * 255.0 + 0.5); }\n"); - } - } - } - - if (!strcmp(fragColor0, "fragColor0")) { - const char *qualifierColor0 = "out"; - if (lastFragData && !strcmp(lastFragData, fragColor0)) { - qualifierColor0 = "inout"; - } - // Output the output color definitions. - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { - WRITE(p, "%s vec4 fragColor0;\n", qualifierColor0); - WRITE(p, "out vec4 fragColor1;\n"); - } else { - WRITE(p, "%s vec4 fragColor0;\n", qualifierColor0); - } - } - - // PowerVR needs a custom modulo function. For some reason, this has far higher precision than the builtin one. - if ((gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) && needShaderTexClamp) { - WRITE(p, "float mymod(float a, float b) { return a - b * floor(a / b); }\n"); - } - - WRITE(p, "void main() {\n"); - - if (isModeClear) { - // Clear mode does not allow any fancy shading. - WRITE(p, " vec4 v = v_color0;\n"); - } else { - const char *secondary = ""; - // Secondary color for specular on top of texture - if (lmode) { - WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); - secondary = " + s"; - } else { - secondary = ""; - } - - if (doTexture) { - const char *texcoord = "v_texcoord"; - // TODO: Not sure the right way to do this for projection. - // This path destroys resolution on older PowerVR no matter what I do if projection is needed, - // so we disable it on SGX 540 and lesser, and live with the consequences. - bool terriblePrecision = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_TERRIBLE) != 0; - bool clampDisabled = doTextureProjection && terriblePrecision; - // Also with terrible precision we can't do wrapping without destroying the image. See #9189 - if (terriblePrecision && (!id.Bit(FS_BIT_CLAMP_S) || !id.Bit(FS_BIT_CLAMP_T))) { - clampDisabled = true; - } - if (needShaderTexClamp && !clampDisabled) { - // We may be clamping inside a larger surface (tex = 64x64, buffer=480x272). - // We may also be wrapping in such a surface, or either one in a too-small surface. - // Obviously, clamping to a smaller surface won't work. But better to clamp to something. - std::string ucoord = "v_texcoord.x"; - std::string vcoord = "v_texcoord.y"; - if (doTextureProjection) { - ucoord = "(v_texcoord.x / v_texcoord.z)"; - vcoord = "(v_texcoord.y / v_texcoord.z)"; - } - - std::string modulo = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? "mymod" : "mod"; - - if (id.Bit(FS_BIT_CLAMP_S)) { - ucoord = "clamp(" + ucoord + ", u_texclamp.z, u_texclamp.x - u_texclamp.z)"; - } else { - ucoord = modulo + "(" + ucoord + ", u_texclamp.x)"; - } - if (id.Bit(FS_BIT_CLAMP_T)) { - vcoord = "clamp(" + vcoord + ", u_texclamp.w, u_texclamp.y - u_texclamp.w)"; - } else { - vcoord = modulo + "(" + vcoord + ", u_texclamp.y)"; - } - if (textureAtOffset) { - ucoord = "(" + ucoord + " + u_texclampoff.x)"; - vcoord = "(" + vcoord + " + u_texclampoff.y)"; - } - - WRITE(p, " vec2 fixedcoord = vec2(%s, %s);\n", ucoord.c_str(), vcoord.c_str()); - texcoord = "fixedcoord"; - // We already projected it. - doTextureProjection = false; - } - - if (!shaderDepal) { - if (doTextureProjection) { - WRITE(p, " vec4 t = %sProj(tex, %s);\n", texture, texcoord); - } else { - WRITE(p, " vec4 t = %s(tex, %s.xy);\n", texture, texcoord); - } - } else { - if (doTextureProjection) { - // We don't use textureProj because we need better control and it's probably not much of a savings anyway. - // However it is good for precision on older hardware like PowerVR. - WRITE(p, " vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord); - } else { - WRITE(p, " vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord); - } - WRITE(p, " vec2 tsize = vec2(textureSize(tex, 0));\n"); - WRITE(p, " vec2 fraction;\n"); - WRITE(p, " bool bilinear = (u_depal >> 31) != 0;\n"); - WRITE(p, " if (bilinear) {\n"); - WRITE(p, " uv_round = uv * tsize - vec2(0.5, 0.5);\n"); - WRITE(p, " fraction = fract(uv_round);\n"); - WRITE(p, " uv_round = (uv_round - fraction + vec2(0.5, 0.5)) / tsize;\n"); // We want to take our four point samples at pixel centers. - WRITE(p, " } else {\n"); - WRITE(p, " uv_round = uv;\n"); - WRITE(p, " }\n"); - WRITE(p, " vec4 t = %s(tex, uv_round);\n", texture); - WRITE(p, " vec4 t1 = %sOffset(tex, uv_round, ivec2(1, 0));\n", texture); - WRITE(p, " vec4 t2 = %sOffset(tex, uv_round, ivec2(0, 1));\n", texture); - WRITE(p, " vec4 t3 = %sOffset(tex, uv_round, ivec2(1, 1));\n", texture); - 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, " 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 && !(index0 == index1 && index1 == index2 && index2 == index3)) {\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) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " vec4 v = p * t%s;\n", secondary); - break; - - case GE_TEXFUNC_DECAL: - WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_BLEND: - WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_REPLACE: - WRITE(p, " vec4 v = t%s;\n", secondary); - break; - - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); - break; - default: - WRITE(p, " vec4 v = p;\n"); break; - } - } else { // texfmt == RGB - switch (texFunc) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_DECAL: - WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_BLEND: - WRITE(p, " vec4 v = vec4(mix(p.rgb, u_texenv.rgb, t.rgb), p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_REPLACE: - WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; - default: - WRITE(p, " vec4 v = p;\n"); break; - } - } - - if (enableColorDoubling) { - // This happens before fog is applied. - WRITE(p, " v.rgb = clamp(v.rgb * 2.0, 0.0, 1.0);\n"); - } - } else { - // No texture mapping - WRITE(p, " vec4 v = v_color0 %s;\n", secondary); - } - - if (enableFog) { - WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); - WRITE(p, " v = mix(vec4(u_fogcolor, v.a), v, fogCoef);\n"); - // WRITE(p, " v.x = v_depth;\n"); - } - - // Texture access is at half texels [0.5/256, 255.5/256], but colors are normalized [0, 255]. - // So we have to scale to account for the difference. - std::string alphaTestXCoord = "0"; - if (g_Config.bFragmentTestCache) { - if (enableColorTest && !colorTestAgainstZero) { - WRITE(p, " vec4 vScale256 = v * %f + %f;\n", 255.0 / 256.0, 0.5 / 256.0); - alphaTestXCoord = "vScale256.a"; - } else if (enableAlphaTest && !alphaTestAgainstZero) { - char temp[64]; - snprintf(temp, sizeof(temp), "v.a * %f + %f", 255.0 / 256.0, 0.5 / 256.0); - alphaTestXCoord = temp; - } - } - - const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "discard;"; - if (enableAlphaTest) { - if (alphaTestAgainstZero) { - // When testing against 0 (extremely common), we can avoid some math. - // 0.002 is approximately half of 1.0 / 255.0. - if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { - WRITE(p, " if (v.a < 0.002) %s\n", discardStatement); - } else if (alphaTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. Happens sometimes, actually... - WRITE(p, " if (v.a > 0.002) %s\n", discardStatement); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, " %s\n", discardStatement); - } - } else if (g_Config.bFragmentTestCache) { - WRITE(p, " float aResult = %s(testtex, vec2(%s, 0)).a;\n", texture, alphaTestXCoord.c_str()); - WRITE(p, " if (aResult < 0.5) %s\n", discardStatement); - } else { - const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; - if (alphaTestFuncs[alphaTestFunc][0] != '#') { - if (bitwiseOps) { - WRITE(p, " if ((roundAndScaleTo255i(v.a) & u_alphacolormask.a) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); - } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { - // Work around bad PVR driver problem where equality check + discard just doesn't work. - if (alphaTestFunc != GE_COMP_NOTEQUAL) { - WRITE(p, " if (roundTo255thf(v.a) %s u_alphacolorref.a) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); - } - } else { - WRITE(p, " if (roundAndScaleTo255f(v.a) %s u_alphacolorref.a) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); - } - } else { - // This means NEVER. See above. - WRITE(p, " %s\n", discardStatement); - } - } - } - - if (enableColorTest) { - if (colorTestAgainstZero) { - // When testing against 0 (common), we can avoid some math. - // 0.002 is approximately half of 1.0 / 255.0. - if (colorTestFunc == GE_COMP_NOTEQUAL) { - WRITE(p, " if (v.r < 0.002 && v.g < 0.002 && v.b < 0.002) %s\n", discardStatement); - } else if (colorTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. - WRITE(p, " if (v.r > 0.002 || v.g > 0.002 || v.b > 0.002) %s\n", discardStatement); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, " %s\n", discardStatement); - } - } else if (g_Config.bFragmentTestCache) { - WRITE(p, " float rResult = %s(testtex, vec2(vScale256.r, 0)).r;\n", texture); - WRITE(p, " float gResult = %s(testtex, vec2(vScale256.g, 0)).g;\n", texture); - WRITE(p, " float bResult = %s(testtex, vec2(vScale256.b, 0)).b;\n", texture); - if (colorTestFunc == GE_COMP_EQUAL) { - // Equal means all parts must be equal (so discard if any is not.) - WRITE(p, " if (rResult < 0.5 || gResult < 0.5 || bResult < 0.5) %s\n", discardStatement); - } else { - // Not equal means any part must be not equal. - WRITE(p, " if (rResult < 0.5 && gResult < 0.5 && bResult < 0.5) %s\n", discardStatement); - } - } else { - const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; - if (colorTestFuncs[colorTestFunc][0] != '#') { - if (bitwiseOps) { - // Apparently GLES3 does not support vector bitwise ops. - WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - const char *maskedFragColor = "ivec3(v_scaled.r & u_alphacolormask.r, v_scaled.g & u_alphacolormask.g, v_scaled.b & u_alphacolormask.b)"; - const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & u_alphacolormask.r, int(u_alphacolorref.g) & u_alphacolormask.g, int(u_alphacolorref.b) & u_alphacolormask.b)"; - WRITE(p, " if (%s %s %s) %s\n", maskedFragColor, colorTestFuncs[colorTestFunc], maskedColorRef, discardStatement); - } else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) { - WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); - } else { - WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement); - } - } else { - WRITE(p, " %s\n", discardStatement); - } - } - } - - if (replaceBlend == REPLACE_BLEND_2X_SRC) { - WRITE(p, " v.rgb = v.rgb * 2.0;\n"); - } - - if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - const char *srcFactor = "ERROR"; - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; - // PRE_SRC for REPLACE_BLEND_PRE_SRC_2X_ALPHA means "double the src." - // It's close to the same, but clamping can still be an issue. - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "vec3(2.0)"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; - default: srcFactor = "u_blendFixA"; break; - } - - WRITE(p, " v.rgb = v.rgb * %s;\n", srcFactor); - } - - if (replaceBlend == REPLACE_BLEND_COPY_FBO) { - // If we have NV_shader_framebuffer_fetch / EXT_shader_framebuffer_fetch, we skip the blit. - // We can just read the prev value more directly. - if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) { - WRITE(p, " lowp vec4 destColor = %s;\n", lastFragData); - } else if (!texelFetch) { - WRITE(p, " lowp vec4 destColor = %s(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", texture); - } else { - WRITE(p, " lowp vec4 destColor = %s(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n", texelFetch); - } - - const char *srcFactor = "vec3(1.0)"; - const char *dstFactor = "vec3(0.0)"; - - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(vec3(1.0) - destColor.rgb)"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "vec3(destColor.a)"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a)"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "vec3(destColor.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a * 2.0)"; break; - case GE_SRCBLEND_FIXA: srcFactor = "u_blendFixA"; break; - default: srcFactor = "u_blendFixA"; break; - } - switch (replaceBlendFuncB) { - case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; - case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(vec3(1.0) - v.rgb)"; break; - case GE_DSTBLEND_SRCALPHA: dstFactor = "vec3(v.a)"; break; - case GE_DSTBLEND_INVSRCALPHA: dstFactor = "vec3(1.0 - v.a)"; break; - case GE_DSTBLEND_DSTALPHA: dstFactor = "vec3(destColor.a)"; break; - case GE_DSTBLEND_INVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a)"; break; - case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "vec3(v.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "vec3(1.0 - v.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "vec3(destColor.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a * 2.0)"; break; - case GE_DSTBLEND_FIXB: dstFactor = "u_blendFixB"; break; - default: srcFactor = "u_blendFixB"; break; - } - - switch (replaceBlendEq) { - case GE_BLENDMODE_MUL_AND_ADD: - WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT: - WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: - WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", dstFactor, srcFactor); - break; - case GE_BLENDMODE_MIN: - WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_MAX: - WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_ABSDIFF: - WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); - break; - } - } - - if (replaceBlend == REPLACE_BLEND_2X_ALPHA || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - WRITE(p, " v.a = v.a * 2.0;\n"); - } - } - - std::string replacedAlpha = "0.0"; - char replacedAlphaTemp[64] = ""; - if (stencilToAlpha != REPLACE_ALPHA_NO) { - switch (replaceAlphaWithStencilType) { - case STENCIL_VALUE_UNIFORM: - replacedAlpha = "u_stencilReplaceValue"; - break; - - case STENCIL_VALUE_ZERO: - replacedAlpha = "0.0"; - break; - - case STENCIL_VALUE_ONE: - case STENCIL_VALUE_INVERT: - // In invert, we subtract by one, but we want to output one here. - replacedAlpha = "1.0"; - break; - - case STENCIL_VALUE_INCR_4: - case STENCIL_VALUE_DECR_4: - // We're adding/subtracting, just by the smallest value in 4-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 15.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_INCR_8: - case STENCIL_VALUE_DECR_8: - // We're adding/subtracting, just by the smallest value in 8-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 255.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_KEEP: - // Do nothing. We'll mask out the alpha using color mask. - break; - } - } - - switch (stencilToAlpha) { - case REPLACE_ALPHA_DUALSOURCE: - WRITE(p, " %s = vec4(v.rgb, %s);\n", fragColor0, replacedAlpha.c_str()); - WRITE(p, " %s = vec4(0.0, 0.0, 0.0, v.a);\n", fragColor1); - break; - - case REPLACE_ALPHA_YES: - WRITE(p, " %s = vec4(v.rgb, %s);\n", fragColor0, replacedAlpha.c_str()); - break; - - case REPLACE_ALPHA_NO: - WRITE(p, " %s = v;\n", fragColor0); - break; - - default: - ERROR_LOG(G3D, "Bad stencil-to-alpha type, corrupt ID?"); - return false; - } - - LogicOpReplaceType replaceLogicOpType = (LogicOpReplaceType)id.Bits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2); - switch (replaceLogicOpType) { - case LOGICOPTYPE_ONE: - WRITE(p, " %s.rgb = vec3(1.0, 1.0, 1.0);\n", fragColor0); - break; - case LOGICOPTYPE_INVERT: - WRITE(p, " %s.rgb = vec3(1.0, 1.0, 1.0) - %s.rgb;\n", fragColor0, fragColor0); - break; - case LOGICOPTYPE_NORMAL: - break; - - default: - ERROR_LOG(G3D, "Bad logic op type, corrupt ID?"); - return false; - } - -#ifdef DEBUG_SHADER - if (doTexture) { - WRITE(p, " %s = texture2D(tex, v_texcoord.xy);\n", fragColor0); - WRITE(p, " %s += vec4(0.3,0,0.3,0.3);\n", fragColor0); - } else { - WRITE(p, " %s = vec4(1,0,1,1);\n", fragColor0); - } -#endif - - if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { - const double scale = DepthSliceFactor() * 65535.0; - - WRITE(p, " highp float z = gl_FragCoord.z;\n"); - if (gstate_c.Supports(GPU_SUPPORTS_ACCURATE_DEPTH)) { - // We center the depth with an offset, but only its fraction matters. - // When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0. - if (((int)(DepthSliceFactor() - 1.0f) & 1) == 1) { - WRITE(p, " z = (floor((z * %f) - (1.0 / 2.0)) + (1.0 / 2.0)) * (1.0 / %f);\n", scale, scale); - } else { - WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale); - } - } else { - WRITE(p, " z = (1.0/65535.0) * floor(z * 65535.0);\n"); - } - if (PSP_CoreParameter().compat.flags().DecreaseFragmentDepthAccuracy) { - WRITE(p, " z /= 2000.0;\n"); // Nasty hack to completely remove flicker in Heroes Phantasia - } - WRITE(p, " gl_FragDepth = z;\n"); - } - - WRITE(p, "}\n"); - - return true; -} - diff --git a/GPU/GLES/FragmentShaderGeneratorGLES.h b/GPU/GLES/FragmentShaderGeneratorGLES.h deleted file mode 100644 index 1d190e5e96..0000000000 --- a/GPU/GLES/FragmentShaderGeneratorGLES.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2012- 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/. - -#pragma once - -struct FShaderID; - -bool GenerateFragmentShader(const FShaderID &id, char *buffer, uint64_t *uniformMask); diff --git a/GPU/GLES/FragmentTestCacheGLES.cpp b/GPU/GLES/FragmentTestCacheGLES.cpp index 47a23efc4d..bd977cf103 100644 --- a/GPU/GLES/FragmentTestCacheGLES.cpp +++ b/GPU/GLES/FragmentTestCacheGLES.cpp @@ -144,7 +144,7 @@ GLRTexture *FragmentTestCacheGLES::CreateTestTexture(const GEComparison funcs[4] } } - GLRTexture *tex = render_->CreateTexture(GL_TEXTURE_2D); + GLRTexture *tex = render_->CreateTexture(GL_TEXTURE_2D, 256, 1, 1); render_->TextureImage(tex, 0, 256, 1, Draw::DataFormat::R8G8B8A8_UNORM, data); return tex; } diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index c551b7756f..a659377d66 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -105,7 +105,7 @@ FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw, GLRender needGLESRebinds_ = true; CreateDeviceObjects(); render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - presentation_->SetLanguage(gl_extensions.IsCoreContext ? GLSL_300 : GLSL_140); + presentation_->SetLanguage(gl_extensions.IsCoreContext ? GLSL_3xx : GLSL_1xx); } void FramebufferManagerGLES::Init() { @@ -242,57 +242,6 @@ void FramebufferManagerGLES::DrawActiveTexture(float x, float y, float w, float } } -void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { - if (!useBufferedRendering_ || !vfb->fbo) { - return; - } - - // Technically, we should at this point re-interpret the bytes of the old format to the new. - // That might get tricky, and could cause unnecessary slowness in some games. - // For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts. - // (it uses 565 to write zeros to the buffer, then 4444 to actually render the shadow.) - // - // The best way to do this may ultimately be to create a new FBO (combine with any resize?) - // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex - // to exactly reproduce in 4444 and 8888 formats. - - if (old == GE_FORMAT_565) { - // Clear alpha and stencil. - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "ReformatFramebuffer"); - render_->Clear(0, 0.0f, 0, GL_COLOR_BUFFER_BIT, 0x8, 0, 0, 0, 0); - } -} - -void FramebufferManagerGLES::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { - if (!framebuffer->fbo || !useBufferedRendering_) { - render_->BindTexture(stage, nullptr); - gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; - return; - } - - // currentRenderVfb_ will always be set when this is called, except from the GE debugger. - // Let's just not bother with the copy in that case. - bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0; - if (GPUStepping::IsStepping()) { - skipCopy = true; - } - if (!skipCopy && framebuffer == currentRenderVfb_) { - // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); - if (renderCopy) { - VirtualFramebuffer copyInfo = *framebuffer; - copyInfo.fbo = renderCopy; - - CopyFramebufferForColorTexture(©Info, framebuffer, flags); - draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0); - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } -} - void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { _assert_msg_(nvfb->fbo, "Expecting a valid nvfb in UpdateDownloadTempBuffer"); @@ -315,8 +264,8 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, bool useBlit = gstate_c.Supports(GPU_SUPPORTS_FRAMEBUFFER_BLIT); - float srcXFactor = useBlit ? (float)src->renderWidth / (float)src->bufferWidth : 1.0f; - float srcYFactor = useBlit ? (float)src->renderHeight / (float)src->bufferHeight : 1.0f; + float srcXFactor = useBlit ? src->renderScaleFactor : 1.0f; + float srcYFactor = useBlit ? src->renderScaleFactor : 1.0f; const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2; if (srcBpp != bpp && bpp != 0) { srcXFactor = (srcXFactor * bpp) / srcBpp; @@ -326,8 +275,8 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int srcY1 = srcY * srcYFactor; int srcY2 = (srcY + h) * srcYFactor; - float dstXFactor = useBlit ? (float)dst->renderWidth / (float)dst->bufferWidth : 1.0f; - float dstYFactor = useBlit ? (float)dst->renderHeight / (float)dst->bufferHeight : 1.0f; + float dstXFactor = useBlit ? dst->renderScaleFactor : 1.0f; + float dstYFactor = useBlit ? dst->renderScaleFactor : 1.0f; const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2; if (dstBpp != bpp && bpp != 0) { dstXFactor = (dstXFactor * bpp) / dstBpp; @@ -347,12 +296,11 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, // glBlitFramebuffer can clip, but glCopyImageSubData is more restricted. // In case the src goes outside, we just skip the optimization in that case. const bool sameSize = dstX2 - dstX1 == srcX2 - srcX1 && dstY2 - dstY1 == srcY2 - srcY1; - const bool sameDepth = dst->colorDepth == src->colorDepth; const bool srcInsideBounds = srcX2 <= src->renderWidth && srcY2 <= src->renderHeight; const bool dstInsideBounds = dstX2 <= dst->renderWidth && dstY2 <= dst->renderHeight; const bool xOverlap = src == dst && srcX2 > dstX1 && srcX1 < dstX2; const bool yOverlap = src == dst && srcY2 > dstY1 && srcY1 < dstY2; - if (sameSize && sameDepth && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { + if (sameSize && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { draw_->CopyFramebufferImage(src->fbo, 0, srcX1, srcY1, 0, dst->fbo, 0, dstX1, dstY1, 0, dstX2 - dstX1, dstY2 - dstY1, 1, Draw::FB_COLOR_BIT, "BlitFramebuffer"); return; } @@ -361,6 +309,8 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (useBlit) { draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST, "BlitFramebuffer"); } else { + // TODO: Move this workaround out into thin3d, instead of dirtying up the code here. + draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "BlitFramebuffer"); draw_->BindFramebufferAsTexture(src->fbo, 0, Draw::FB_COLOR_BIT, 0); @@ -377,7 +327,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, float srcW = src->bufferWidth; float srcH = src->bufferHeight; render_->BindProgram(draw2dprogram_); - DrawActiveTexture(dstX1, dstY1, w * dstXFactor, h, dst->bufferWidth, dst->bufferHeight, srcX1 / srcW, srcY1 / srcH, srcX2 / srcW, srcY2 / srcH, ROTATION_LOCKED_HORIZONTAL, DRAWTEX_NEAREST); + DrawActiveTexture(dstX1, dstY1, w * dstXFactor, h * dstYFactor, dst->bufferWidth, dst->bufferHeight, srcX1 / srcW, srcY1 / srcH, srcX2 / srcW, srcY2 / srcH, ROTATION_LOCKED_HORIZONTAL, DRAWTEX_NEAREST); textureCacheGL_->ForgetLastTexture(); } @@ -388,16 +338,14 @@ void FramebufferManagerGLES::EndFrame() { } void FramebufferManagerGLES::DeviceLost() { - DestroyAllFBOs(); + FramebufferManagerCommon::DeviceLost(); DestroyDeviceObjects(); - presentation_->DeviceLost(); } void FramebufferManagerGLES::DeviceRestore(Draw::DrawContext *draw) { - draw_ = draw; - presentation_->DeviceRestore(draw); - render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + FramebufferManagerCommon::DeviceRestore(draw); CreateDeviceObjects(); + render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); } void FramebufferManagerGLES::Resized() { diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index 51d44af73c..72c91f4b9b 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -46,18 +46,14 @@ public: virtual void Init() override; void EndFrame(); void Resized() override; - void DeviceLost(); - void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) override; - // For use when texturing from a framebuffer. May create a duplicate if target. - void BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; bool NotifyStencilUpload(u32 addr, int size, StencilUpload flags = StencilUpload::NEEDS_CLEAR) override; bool GetOutputFramebuffer(GPUDebugBuffer &buffer) override; - void DeviceRestore(Draw::DrawContext *draw); - protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp) override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index acbd172dfa..192c7f832b 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -175,7 +175,7 @@ void GPU_GLES::CheckGPUFeatures() { } } - if (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.NV_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch) { + if (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch) { // This has caused problems in the past. Let's only enable on GLES3. if (features & GPU_SUPPORTS_GLSL_ES_300) { features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH; @@ -183,27 +183,12 @@ void GPU_GLES::CheckGPUFeatures() { } if (gl_extensions.ARB_framebuffer_object || gl_extensions.NV_framebuffer_blit || gl_extensions.GLES3) { - features |= GPU_SUPPORTS_FRAMEBUFFER_BLIT; + features |= GPU_SUPPORTS_FRAMEBUFFER_BLIT | GPU_SUPPORTS_FRAMEBUFFER_BLIT_TO_DEPTH; } if (gl_extensions.ARB_vertex_array_object && gl_extensions.IsCoreContext) { features |= GPU_SUPPORTS_VAO; } - bool useCPU = false; - if (!gl_extensions.IsGLES) { - // Some cards or drivers seem to always dither when downloading a framebuffer to 16-bit. - // This causes glitches in games that expect the exact values. - // It has not been experienced on NVIDIA cards, so those are left using the GPU (which is faster.) - if (gl_extensions.gpuVendor != GPU_VENDOR_NVIDIA || !gl_extensions.VersionGEThan(3, 0)) { - useCPU = true; - } - } else { - useCPU = true; - } - - if (useCPU) - features |= GPU_PREFER_CPU_DOWNLOAD; - if ((gl_extensions.gpuVendor == GPU_VENDOR_NVIDIA) || (gl_extensions.gpuVendor == GPU_VENDOR_AMD)) features |= GPU_PREFER_REVERSE_COLOR_ORDER; @@ -315,21 +300,18 @@ void GPU_GLES::DeviceLost() { fragmentTestCache_.DeviceLost(); depalShaderCache_.DeviceLost(); drawEngine_.DeviceLost(); - framebufferManagerGL_->DeviceLost(); - // Don't even try to access the lost device. - draw_ = nullptr; + + GPUCommon::DeviceLost(); } void GPU_GLES::DeviceRestore() { - draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext(); - INFO_LOG(G3D, "GPU_GLES: DeviceRestore"); + GPUCommon::DeviceRestore(); UpdateCmdInfo(); UpdateVsyncInterval(true); shaderManagerGL_->DeviceRestore(draw_); textureCacheGL_->DeviceRestore(draw_); - framebufferManagerGL_->DeviceRestore(draw_); drawEngine_.DeviceRestore(draw_); fragmentTestCache_.DeviceRestore(draw_); depalShaderCache_.DeviceRestore(draw_); @@ -400,16 +382,6 @@ void GPU_GLES::CopyDisplayToOutput(bool reallyDirty) { framebufferManagerGL_->CopyDisplayToOutput(reallyDirty); framebufferManagerGL_->EndFrame(); - - // If buffered, discard the depth buffer of the backbuffer. Don't even know if we need one. -#if 0 -#ifdef USING_GLES2 - if (gl_extensions.EXT_discard_framebuffer && framebufferManager_->UseBufferedRendering()) { - GLenum attachments[] = {GL_DEPTH_EXT, GL_STENCIL_EXT}; - glDiscardFramebufferEXT(GL_FRAMEBUFFER, 2, attachments); - } -#endif -#endif } void GPU_GLES::FinishDeferred() { diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index e4e3d4fda4..eccc9f8834 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -30,6 +30,7 @@ #include "Common/Math/math_util.h" #include "Common/Math/lin/matrix4x4.h" #include "Common/Profiler/Profiler.h" +#include "Common/GPU/Shader.h" #include "Common/GPU/thin3d.h" #include "Common/GPU/OpenGL/GLRenderManager.h" @@ -101,6 +102,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, queries.push_back({ &u_fogcoef, "u_fogcoef" }); queries.push_back({ &u_alphacolorref, "u_alphacolorref" }); queries.push_back({ &u_alphacolormask, "u_alphacolormask" }); + queries.push_back({ &u_colorWriteMask, "u_colorWriteMask" }); queries.push_back({ &u_stencilReplaceValue, "u_stencilReplaceValue" }); queries.push_back({ &u_testtex, "testtex" }); @@ -166,7 +168,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, queries.push_back({ &u_tess_weights_u, "u_tess_weights_u" }); queries.push_back({ &u_tess_weights_v, "u_tess_weights_v" }); queries.push_back({ &u_spline_counts, "u_spline_counts" }); - queries.push_back({ &u_depal, "u_depal" }); + queries.push_back({ &u_depal_mask_shift_off_fmt, "u_depal_mask_shift_off_fmt" }); attrMask = vs->GetAttrMask(); availableUniforms = vs->GetUniformMask() | fs->GetUniformMask(); @@ -264,7 +266,7 @@ static void SetFloatUniform4(GLRenderManager *render, GLint *uniform, float data static void SetMatrix4x3(GLRenderManager *render, GLint *uniform, const float *m4x3) { float m4x4[16]; - ConvertMatrix4x3To4x4(m4x4, m4x3); + ConvertMatrix4x3To4x4Transposed(m4x4, m4x3); render->SetUniformM4x4(uniform, m4x4); } @@ -298,7 +300,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu 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); + render_->SetUniformUI1(&u_depal_mask_shift_off_fmt, val); } // Update any dirty uniforms before we draw @@ -376,6 +378,9 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu if (dirty & DIRTY_ALPHACOLORMASK) { SetColorUniform3iAlpha(render_, &u_alphacolormask, gstate.colortestmask, gstate.getAlphaTestMask()); } + if (dirty & DIRTY_COLORWRITEMASK) { + render_->SetUniformUI1(&u_colorWriteMask, ~((gstate.pmska << 24) | (gstate.pmskc & 0xFFFFFF))); + } if (dirty & DIRTY_FOGCOLOR) { SetColorUniform3(render_, &u_fogcolor, gstate.fogcolor); } @@ -495,7 +500,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu float bonetemp[16]; for (int i = 0; i < numBones; i++) { if (dirty & (DIRTY_BONEMATRIX0 << i)) { - ConvertMatrix4x3To4x4(bonetemp, gstate.boneMatrix + 12 * i); + ConvertMatrix4x3To4x4Transposed(bonetemp, gstate.boneMatrix + 12 * i); render_->SetUniformM4x4(&u_bone[i], bonetemp); } } @@ -571,7 +576,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu } ShaderManagerGLES::ShaderManagerGLES(Draw::DrawContext *draw) - : ShaderManagerCommon(draw), lastShader_(nullptr), shaderSwitchDirtyUniforms_(0), diskCacheDirty_(false), fsCache_(16), vsCache_(16) { + : ShaderManagerCommon(draw), fsCache_(16), vsCache_(16) { render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); codeBuffer_ = new char[16384]; lastFSID_.set_invalid(); @@ -629,7 +634,9 @@ void ShaderManagerGLES::DirtyLastShader() { Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) { uint64_t uniformMask; - if (!GenerateFragmentShader(FSID, codeBuffer_, &uniformMask)) { + std::string errorString; + if (!GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &uniformMask, &errorString)) { + ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str()); return nullptr; } std::string desc = FragmentShaderDesc(FSID); @@ -640,7 +647,11 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) { bool useHWTransform = VSID.Bit(VS_BIT_USE_HW_TRANSFORM); uint32_t attrMask; uint64_t uniformMask; - GenerateVertexShader(VSID, codeBuffer_, &attrMask, &uniformMask); + std::string errorString; + if (!GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), &attrMask, &uniformMask, &errorString)) { + ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str()); + return nullptr; + } std::string desc = VertexShaderDesc(VSID); return new Shader(render_, codeBuffer_, desc, GL_VERTEX_SHADER, useHWTransform, attrMask, uniformMask); } @@ -716,6 +727,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, Shader *fs = fsCache_.Get(FSID); if (!fs) { // Fragment shader not in cache. Let's compile it. + // Can't really tell if we succeeded since the compile is on the GPU thread later. fs = CompileFragmentShader(FSID); fsCache_.Insert(FSID, fs); diskCacheDirty_ = true; @@ -826,7 +838,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 14 +#define CACHE_VERSION 15 struct CacheHeader { uint32_t magic; uint32_t version; diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index 613fab8eaf..f93c9a4d24 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -23,24 +23,11 @@ #include "Common/GPU/OpenGL/GLRenderManager.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/ShaderId.h" -#include "GPU/GLES/VertexShaderGeneratorGLES.h" -#include "GPU/GLES/FragmentShaderGeneratorGLES.h" +#include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/FragmentShaderGenerator.h" class Shader; -// Pre-fetched attrs and uniforms -enum { - ATTR_POSITION = 0, - ATTR_TEXCOORD = 1, - ATTR_NORMAL = 2, - ATTR_W1 = 3, - ATTR_W2 = 4, - ATTR_COLOR0 = 5, - ATTR_COLOR1 = 6, - - ATTR_COUNT, -}; - class LinkedShader { public: LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs, FShaderID FSID, Shader *fs, bool useHWTransform, bool preloading = false); @@ -88,11 +75,12 @@ public: // Shader depal int u_pal; // the texture - int u_depal; // the params + int u_depal_mask_shift_off_fmt; // the params // Fragment processing inputs int u_alphacolorref; int u_alphacolormask; + int u_colorWriteMask; int u_testtex; int u_fogcolor; int u_fogcoef; @@ -202,8 +190,8 @@ private: FShaderID lastFSID_; VShaderID lastVSID_; - LinkedShader *lastShader_; - u64 shaderSwitchDirtyUniforms_; + LinkedShader *lastShader_ = nullptr; + u64 shaderSwitchDirtyUniforms_ = 0; char *codeBuffer_; typedef DenseHashMap FSCache; @@ -212,7 +200,7 @@ private: typedef DenseHashMap VSCache; VSCache vsCache_; - bool diskCacheDirty_; + bool diskCacheDirty_ = false; struct { std::vector vert; std::vector frag; diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index 46883171c7..cc89eda9b7 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -24,6 +24,7 @@ #include "Common/Profiler/Profiler.h" #include "Common/GPU/OpenGL/GLDebugLog.h" #include "Common/GPU/OpenGL/GLRenderManager.h" +#include "Common/Data/Convert/SmallDataConvert.h" #include "GPU/Math3D.h" #include "GPU/GPUState.h" @@ -35,7 +36,7 @@ #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/GLES/FramebufferManagerGLES.h" -#include "GPU/GLES/FragmentShaderGeneratorGLES.h" +#include "GPU/Common/FragmentShaderGenerator.h" static const GLushort glBlendFactorLookup[(size_t)BlendFactor::COUNT] = { GL_ZERO, @@ -121,7 +122,7 @@ static const GLushort logicOps[] = { }; #endif -inline void DrawEngineGLES::ResetShaderBlending() { +inline void DrawEngineGLES::ResetFramebufferRead() { if (fboTexBound_) { GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager->BindTexture(TEX_SLOT_SHADERBLEND_SRC, nullptr); @@ -140,23 +141,11 @@ void DrawEngineGLES::ApplyDrawState(int prim) { // Start profiling here to skip SetTexture which is already accounted for PROFILE_THIS_SCOPE("applydrawstate"); - // amask is needed for both stencil and blend state so we keep it outside for now - bool amask = (gstate.pmska & 0xFF) < 128; - // Let's not write to alpha if stencil isn't enabled. - if (IsStencilTestOutputDisabled()) { - amask = false; - } else { - // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. - if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { - amask = false; - } - } - bool useBufferedRendering = framebufferManager_->UseBufferedRendering(); if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) { gstate_c.Clean(DIRTY_BLEND_STATE); - gstate_c.SetAllowShaderBlend(!g_Config.bDisableSlowFramebufEffects); + gstate_c.SetAllowFramebufferRead(!g_Config.bDisableSlowFramebufEffects); if (gstate.isModeClear()) { // Color Test @@ -167,22 +156,24 @@ void DrawEngineGLES::ApplyDrawState(int prim) { // Do the large chunks of state conversion. We might be able to hide these two behind a dirty-flag each, // to avoid recomputing heavy stuff unnecessarily every draw call. GenericBlendState blendState; - ConvertBlendState(blendState, gstate_c.allowShaderBlend); + ConvertBlendState(blendState, gstate_c.allowFramebufferRead); - if (blendState.applyShaderBlending) { - if (ApplyShaderBlending()) { - // We may still want to do something about stencil -> alpha. - ApplyStencilReplaceAndLogicOp(blendState.replaceAlphaWithStencil, blendState); + GenericMaskState maskState; + ConvertMaskState(maskState, gstate_c.allowFramebufferRead); + + if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { + if (ApplyFramebufferRead(&fboTexNeedsBind_)) { + // The shader takes over the responsibility for blending, so recompute. + ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); // We copy the framebuffer here, as doing so will wipe any blend state if we do it later. - if (fboTexNeedBind_) { + if (fboTexNeedsBind_) { // Note that this is positions, not UVs, that we need the copy from. - // TODO: If the device doesn't support blit, this will corrupt the currently applied texture. framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); // If we are rendering at a higher resolution, linear is probably best for the dest color. renderManager->SetTextureSampler(1, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR, 0.0f); fboTexBound_ = true; - fboTexNeedBind_ = false; + fboTexNeedsBind_ = false; framebufferManager_->RebindFramebuffer("RebindFramebuffer - ApplyDrawState"); // Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects. @@ -190,49 +181,28 @@ void DrawEngineGLES::ApplyDrawState(int prim) { } } else { // Until next time, force it off. - ResetShaderBlending(); - gstate_c.SetAllowShaderBlend(false); + ResetFramebufferRead(); + gstate_c.SetAllowFramebufferRead(false); } - } else if (blendState.resetShaderBlending) { - ResetShaderBlending(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); + } else if (blendState.resetFramebufferRead) { + ResetFramebufferRead(); } if (blendState.enabled) { - if (blendState.dirtyShaderBlend) { + if (blendState.dirtyShaderBlendFixValues) { + // Not quite sure how necessary this is. gstate_c.Dirty(DIRTY_SHADERBLEND); } if (blendState.useBlendColor) { uint32_t color = blendState.blendColor; - const float col[4] = { - (float)((color & 0xFF) >> 0) * (1.0f / 255.0f), - (float)((color & 0xFF00) >> 8) * (1.0f / 255.0f), - (float)((color & 0xFF0000) >> 16) * (1.0f / 255.0f), - (float)((color & 0xFF000000) >> 24) * (1.0f / 255.0f), - }; + float col[4]; + Uint8x4ToFloat4(col, color); renderManager->SetBlendFactor(col); } } - // PSP color/alpha mask is per bit but we can only support per byte. - // But let's do that, at least. And let's try a threshold. - bool rmask = (gstate.pmskc & 0xFF) < 128; - bool gmask = ((gstate.pmskc >> 8) & 0xFF) < 128; - bool bmask = ((gstate.pmskc >> 16) & 0xFF) < 128; - -#ifndef MOBILE_DEVICE - u8 abits = (gstate.pmska >> 0) & 0xFF; - u8 rbits = (gstate.pmskc >> 0) & 0xFF; - u8 gbits = (gstate.pmskc >> 8) & 0xFF; - u8 bbits = (gstate.pmskc >> 16) & 0xFF; - if ((rbits != 0 && rbits != 0xFF) || (gbits != 0 && gbits != 0xFF) || (bbits != 0 && bbits != 0xFF)) { - WARN_LOG_REPORT_ONCE(rgbmask, G3D, "Unsupported RGB mask: r=%02x g=%02x b=%02x", rbits, gbits, bbits); - } - if (abits != 0 && abits != 0xFF) { - // The stencil part of the mask is supported. - WARN_LOG_REPORT_ONCE(amask, G3D, "Unsupported alpha/stencil mask: %02x", abits); - } -#endif - int mask = (int)rmask | ((int)gmask << 1) | ((int)bmask << 2) | ((int)amask << 3); + int mask = (int)maskState.rgba[0] | ((int)maskState.rgba[1] << 1) | ((int)maskState.rgba[2] << 2) | ((int)maskState.rgba[3] << 3); if (blendState.enabled) { renderManager->SetBlendAndMask(mask, blendState.enabled, glBlendFactorLookup[(size_t)blendState.srcColor], glBlendFactorLookup[(size_t)blendState.dstColor], diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index 1c19fa09e8..df9cd06193 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -170,7 +170,7 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, StencilUplo Draw::Framebuffer *blitFBO = nullptr; if (useBlit) { - blitFBO = GetTempFBO(TempFBO::STENCIL, w, h, Draw::FBO_8888); + blitFBO = GetTempFBO(TempFBO::STENCIL, w, h); draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "NotifyStencilUpload_Blit"); } else if (dstBuffer->fbo) { draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::DONT_CARE }, "NotifyStencilUpload_NoBlit"); @@ -214,6 +214,7 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, StencilUplo } if (useBlit) { + render_->SetScissor({ 0, 0, dstBuffer->renderWidth, dstBuffer->renderHeight }); draw_->BlitFramebuffer(blitFBO, 0, 0, w, h, dstBuffer->fbo, 0, 0, dstBuffer->renderWidth, dstBuffer->renderHeight, Draw::FB_STENCIL_BIT, Draw::FB_BLIT_NEAREST, "NotifyStencilUpload_Blit"); } diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 03c1556376..fcb9ca690d 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -33,7 +33,7 @@ #include "GPU/GPUState.h" #include "GPU/GLES/TextureCacheGLES.h" #include "GPU/GLES/FramebufferManagerGLES.h" -#include "GPU/GLES/FragmentShaderGeneratorGLES.h" +#include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/GLES/DepalettizeShaderGLES.h" #include "GPU/GLES/ShaderManagerGLES.h" #include "GPU/GLES/DrawEngineGLES.h" @@ -347,10 +347,11 @@ void TextureCacheGLES::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, uint32_t clutMode = gstate.clutformat & 0xFFFFFF; bool need_depalettize = IsClutFormat(texFormat); - bool useShaderDepal = framebufferManager_->GetCurrentRenderVFB() != framebuffer && gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300); + bool useShaderDepal = framebufferManager_->GetCurrentRenderVFB() != framebuffer && (gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300) || gstate_c.Supports(GPU_SUPPORTS_GLSL_330)); if (!gstate_c.Supports(GPU_SUPPORTS_32BIT_INT_FSHADER)) { useShaderDepal = false; } + if (need_depalettize && !g_Config.bDisableSlowFramebufEffects) { if (useShaderDepal) { const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); @@ -383,7 +384,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); GLRTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); - Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); render_->SetScissor(GLRect2D{ 0, 0, (int)framebuffer->renderWidth, (int)framebuffer->renderHeight }); @@ -445,11 +446,6 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { // For the estimate, we assume cluts always point to 8888 for simplicity. cacheSizeEstimate_ += EstimateTexMemoryUsage(entry); - // Always generate a texture name unless it's a framebuffer, we might need it if the texture is replaced later. - if (!entry->textureName) { - entry->textureName = render_->CreateTexture(GL_TEXTURE_2D); - } - if ((entry->bufw == 0 || (gstate.texbufwidth[0] & 0xf800) != 0) && entry->addr >= PSP_GetKernelMemoryEnd()) { ERROR_LOG_REPORT(G3D, "Texture with unexpected bufw (full=%d)", gstate.texbufwidth[0] & 0xffff); // Proceeding here can cause a crash. @@ -542,7 +538,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { texelsScaledThisFrame_ += w * h; } } - + // GLES2 doesn't have support for a "Max lod" which is critical as PSP games often // don't specify mips all the way down. As a result, we either need to manually generate // the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not @@ -553,10 +549,11 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { // NOTE: Since the level is not part of the cache key, we assume it never changes. u8 level = std::max(0, gstate.getTexLevelOffset16() / 16); LoadTextureLevel(*entry, replaced, level, scaleFactor, dstFmt); - } else + } else { LoadTextureLevel(*entry, replaced, 0, scaleFactor, dstFmt); + } - // Mipmapping only enable when texture scaling disable + // Mipmapping is only enabled when texture scaling is disabled. int texMaxLevel = 0; bool genMips = false; if (maxLevel > 0 && scaleFactor == 1) { @@ -651,6 +648,14 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r gpuStats.numTexturesDecoded++; + if (!entry.textureName) { + // TODO: Actually pass in correct size here. The size here is not yet used for anything else + // than determining if we can wrap this texture size, that is, it's pow2 or not on very old hardware, else true. + // This will be easy after .. well, yet another refactoring, where I hoist the size calculation out of LoadTextureLevel + // and unify BuildTexture. + entry.textureName = render_->CreateTexture(GL_TEXTURE_2D, 16, 16, 1); + } + if (replaced.GetSize(level, w, h)) { PROFILE_THIS_SCOPE("replacetex"); @@ -715,7 +720,6 @@ void TextureCacheGLES::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &r } bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { -#ifndef USING_GLES2 GPUgstate saved; if (level != 0) { saved = gstate; @@ -776,9 +780,6 @@ bool TextureCacheGLES::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) framebufferManager_->RebindFramebuffer("RebindFramebuffer - GetCurrentTextureDebug"); return result; -#else - return false; -#endif } void TextureCacheGLES::DeviceLost() { diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 1b3ba422e3..674db91b8a 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -75,7 +75,7 @@ private: void LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &replaced, int level, int scaleFactor, Draw::DataFormat dstFmt); Draw::DataFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - TexCacheEntry::TexStatus CheckAlpha(const uint8_t *pixelData, Draw::DataFormat dstFmt, int stride, int w, int h); + static TexCacheEntry::TexStatus CheckAlpha(const uint8_t *pixelData, Draw::DataFormat dstFmt, int stride, int w, int h); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, FramebufferNotificationChannel channel) override; diff --git a/GPU/GLES/VertexShaderGeneratorGLES.cpp b/GPU/GLES/VertexShaderGeneratorGLES.cpp deleted file mode 100644 index 188dd3c706..0000000000 --- a/GPU/GLES/VertexShaderGeneratorGLES.cpp +++ /dev/null @@ -1,858 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include -#include -#include - -#include "Common/GPU/OpenGL/GLFeatures.h" - -#if defined(_WIN32) && defined(_DEBUG) -#include "Common/CommonWindows.h" -#endif - -#include "Common/StringUtils.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" -#include "Core/Config.h" -#include "Core/System.h" -#include "GPU/GLES/VertexShaderGeneratorGLES.h" -#include "GPU/GLES/ShaderManagerGLES.h" -#include "GPU/Common/ShaderId.h" -#include "GPU/Common/VertexDecoderCommon.h" - -#undef WRITE - -#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, - LIGHT_FULL, -}; - - -// Depth range and viewport -// -// After the multiplication with the projection matrix, we have a 4D vector in clip space. -// In OpenGL, Z is from -1 to 1, while in D3D, Z is from 0 to 1. -// PSP appears to use the OpenGL convention. As Z is from -1 to 1, and the viewport is represented -// by a center and a scale, to find the final Z value, all we need to do is to multiply by ZScale and -// add ZCenter - these are properly scaled to directly give a Z value in [0, 65535]. -// -// z = vec.z * ViewportZScale + ViewportZCenter; -// -// That will give us the final value between 0 and 65535, which we can simply floor to simulate -// the limited precision of the PSP's depth buffer. Then we convert it back: -// z = floor(z); -// -// vec.z = (z - ViewportZCenter) / ViewportZScale; -// -// Now, the regular machinery will take over and do the calculation again. -// -// Depth is not clipped to the viewport, but does clip to "minz" and "maxz". It may also be clamped -// to 0 and 65535 if a depth clamping/clipping flag is set (x/y clipping is performed only if depth -// needs to be clamped.) -// -// All this above is for full transform mode. -// In through mode, the Z coordinate just goes straight through and there is no perspective division. -// We simulate this of course with pretty much an identity matrix. Rounding Z becomes very easy. -// -// TODO: Skip all this if we can actually get a 16-bit depth buffer along with stencil, which -// is a bit of a rare configuration, although quite common on mobile. - - -void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, uint64_t *uniformMask) { - char *p = buffer; - *attrMask = 0; - *uniformMask = 0; - // #define USE_FOR_LOOP - - // In GLSL ES 3.0, you use "out" variables instead. - 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; - - if (gl_extensions.IsGLES) { - if (gstate_c.Supports(GPU_SUPPORTS_GLSL_ES_300)) { - WRITE(p, "#version 300 es\n"); - glslES30 = true; - texelFetch = "texelFetch"; - } else { - WRITE(p, "#version 100\n"); // GLSL ES 1.0 - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - texelFetch = "texelFetch2D"; - } - } - WRITE(p, "precision highp float;\n"); - - // PowerVR needs highp to do the fog in MHU correctly. - // Others don't, and some can't handle highp in the fragment shader. - highpFog = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? true : false; - highpTexcoord = highpFog; - } else { - if (!gl_extensions.ForceGL2 || gl_extensions.IsCoreContext) { - if (gl_extensions.VersionGEThan(3, 3, 0)) { - glslES30 = true; - WRITE(p, "#version 330\n"); - texelFetch = "texelFetch"; - } else if (gl_extensions.VersionGEThan(3, 0, 0)) { - WRITE(p, "#version 130\n"); - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - texelFetch = "texelFetch"; - } - } else { - WRITE(p, "#version 110\n"); - if (gl_extensions.EXT_gpu_shader4) { - WRITE(p, "#extension GL_EXT_gpu_shader4 : enable\n"); - texelFetch = "texelFetch2D"; - } - } - } - - // We remove these everywhere - GL4, GL3, Mac-forced-GL2, etc. - WRITE(p, "#define lowp\n"); - WRITE(p, "#define mediump\n"); - WRITE(p, "#define highp\n"); - } - - if (glslES30 || gl_extensions.IsCoreContext) { - attribute = "in"; - varying = "out"; - boneWeightDecl = boneWeightInDecl; - } - - bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH); - bool lmode = id.Bit(VS_BIT_LMODE); - bool doTexture = id.Bit(VS_BIT_DO_TEXTURE); - bool doTextureProjection = id.Bit(VS_BIT_DO_TEXTURE_TRANSFORM); - - GETexMapMode uvGenMode = static_cast(id.Bits(VS_BIT_UVGEN_MODE, 2)); - - // this is only valid for some settings of uvGenMode - GETexProjMapMode uvProjMode = static_cast(id.Bits(VS_BIT_UVPROJ_MODE, 2)); - bool doShadeMapping = uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP; - bool doFlatShading = id.Bit(VS_BIT_FLATSHADE); - - bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); - bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; - bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform; - bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform; - bool enableFog = id.Bit(VS_BIT_ENABLE_FOG); - 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); - - bool doBezier = id.Bit(VS_BIT_BEZIER); - bool doSpline = id.Bit(VS_BIT_SPLINE); - bool hasColorTess = id.Bit(VS_BIT_HAS_COLOR_TESS); - bool hasTexcoordTess = id.Bit(VS_BIT_HAS_TEXCOORD_TESS); - bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS); - bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS); - - const char *shading = ""; - if (glslES30) - shading = doFlatShading ? "flat " : ""; - - DoLightComputation doLight[4] = { LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF }; - if (useHWTransform) { - int shadeLight0 = doShadeMapping ? ls0 : -1; - int shadeLight1 = doShadeMapping ? ls1 : -1; - for (int i = 0; i < 4; i++) { - if (i == shadeLight0 || i == shadeLight1) - doLight[i] = LIGHT_SHADE; - if (enableLighting && id.Bit(VS_BIT_LIGHT0_ENABLE + i)) - doLight[i] = LIGHT_FULL; - } - } - - 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 - WRITE(p, "%s vec4 position;\n", attribute); // need to pass the fog coord in w - *attrMask |= 1 << ATTR_POSITION; - - if (useHWTransform && hasNormal) { - WRITE(p, "%s mediump vec3 normal;\n", attribute); - *attrMask |= 1 << ATTR_NORMAL; - } - - bool texcoordVec3In = false; - if (doTexture && hasTexcoord) { - if (!useHWTransform && doTextureProjection && !isModeThrough) { - WRITE(p, "%s vec3 texcoord;\n", attribute); - texcoordVec3In = true; - } else { - WRITE(p, "%s vec2 texcoord;\n", attribute); - } - *attrMask |= 1 << ATTR_TEXCOORD; - } - if (hasColor) { - WRITE(p, "%s lowp vec4 color0;\n", attribute); - *attrMask |= 1 << ATTR_COLOR0; - if (lmode && !useHWTransform) { // only software transform supplies color1 as vertex data - WRITE(p, "%s lowp vec3 color1;\n", attribute); - *attrMask |= 1 << ATTR_COLOR1; - } - } - - if (isModeThrough) { - WRITE(p, "uniform mat4 u_proj_through;\n"); - *uniformMask |= DIRTY_PROJTHROUGHMATRIX; - } else { - WRITE(p, "uniform mat4 u_proj;\n"); - *uniformMask |= DIRTY_PROJMATRIX; - // Add all the uniforms we'll need to transform properly. - } - - bool scaleUV = !isModeThrough && (uvGenMode == GE_TEXMAP_TEXTURE_COORDS || uvGenMode == GE_TEXMAP_UNKNOWN); - - if (useHWTransform) { - // When transforming by hardware, we need a great deal more uniforms... - WRITE(p, "uniform mat4 u_world;\n"); - WRITE(p, "uniform mat4 u_view;\n"); - *uniformMask |= DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX; - if (doTextureProjection) { - 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; - } - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_OFF) { - // This is needed for shade mapping - WRITE(p, "uniform vec3 u_lightpos%i;\n", i); - *uniformMask |= DIRTY_LIGHT0 << i; - } - if (doLight[i] == LIGHT_FULL) { - *uniformMask |= DIRTY_LIGHT0 << i; - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - - if (type != GE_LIGHTTYPE_DIRECTIONAL) - WRITE(p, "uniform mediump vec3 u_lightatt%i;\n", i); - - if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) { - WRITE(p, "uniform mediump vec3 u_lightdir%i;\n", i); - WRITE(p, "uniform mediump vec2 u_lightangle_spotCoef%i;\n", i); - } - WRITE(p, "uniform lowp vec3 u_lightambient%i;\n", i); - WRITE(p, "uniform lowp vec3 u_lightdiffuse%i;\n", i); - - if (comp == GE_LIGHTCOMP_BOTH) { - WRITE(p, "uniform lowp vec3 u_lightspecular%i;\n", i); - } - } - } - if (enableLighting) { - WRITE(p, "uniform lowp vec4 u_ambient;\n"); - *uniformMask |= DIRTY_AMBIENT; - if ((matUpdate & 2) == 0 || !hasColor) { - WRITE(p, "uniform lowp vec3 u_matdiffuse;\n"); - *uniformMask |= DIRTY_MATDIFFUSE; - } - WRITE(p, "uniform lowp vec4 u_matspecular;\n"); // Specular coef is contained in alpha - WRITE(p, "uniform lowp vec3 u_matemissive;\n"); - *uniformMask |= DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE; - } - } - - if (useHWTransform || !hasColor) { - WRITE(p, "uniform lowp vec4 u_matambientalpha;\n"); // matambient + matalpha - *uniformMask |= DIRTY_MATAMBIENTALPHA; - } - if (enableFog) { - WRITE(p, "uniform highp vec2 u_fogcoef;\n"); - *uniformMask |= DIRTY_FOGCOEF; - } - - if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, "uniform highp vec4 u_depthRange;\n"); - *uniformMask |= DIRTY_DEPTHRANGE; - } - - if (!isModeThrough) { - WRITE(p, "uniform highp vec4 u_cullRangeMin;\n"); - WRITE(p, "uniform highp vec4 u_cullRangeMax;\n"); - *uniformMask |= DIRTY_CULLRANGE; - } - - WRITE(p, "%s%s lowp vec4 v_color0;\n", shading, varying); - if (lmode) { - WRITE(p, "%s%s lowp vec3 v_color1;\n", shading, varying); - } - - if (doTexture) { - WRITE(p, "%s %s vec3 v_texcoord;\n", varying, highpTexcoord ? "highp" : "mediump"); - } - - if (enableFog) { - // See the fragment shader generator - if (highpFog) { - WRITE(p, "%s highp float v_fogdepth;\n", varying); - } else { - WRITE(p, "%s mediump float v_fogdepth;\n", varying); - } - } - - // See comment above this function (GenerateVertexShader). - if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. - WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n"); - WRITE(p, " float z = v.z / v.w;\n"); - WRITE(p, " z = z * u_depthRange.x + u_depthRange.y;\n"); - WRITE(p, " z = floor(z);\n"); - WRITE(p, " z = (z - u_depthRange.z) * u_depthRange.w;\n"); - if (PSP_CoreParameter().compat.flags().DecreaseVertexDepthAccuracy) { - WRITE(p, " z /= 1.000031;\n"); // Hokuto no Ken: Raoh Gaiden - Ten no Haoh - } - WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n"); - WRITE(p, "}\n\n"); - } - - // Hardware tessellation - if (doBezier || doSpline) { - *uniformMask |= DIRTY_BEZIERSPLINE; - - WRITE(p, "uniform sampler2D u_tess_points;\n"); // Control Points - WRITE(p, "uniform sampler2D u_tess_weights_u;\n"); - WRITE(p, "uniform sampler2D u_tess_weights_v;\n"); - - WRITE(p, "uniform int u_spline_counts;\n"); - - for (int i = 2; i <= 4; i++) { - // Define 3 types vec2, vec3, vec4 - WRITE(p, "vec%d tess_sample(in vec%d points[16], mat4 weights) {\n", i, i); - WRITE(p, " vec%d pos = vec%d(0.0);\n", i, i); - for (int v = 0; v < 4; ++v) { - for (int u = 0; u < 4; ++u) { - WRITE(p, " pos += weights[%i][%i] * points[%i];\n", v, u, v * 4 + u); - } - } - WRITE(p, " return pos;\n"); - WRITE(p, "}\n"); - } - - if (!gl_extensions.VersionGEThan(3, 0, 0)) { // For glsl version 1.10 - WRITE(p, "mat4 outerProduct(vec4 u, vec4 v) {\n"); - WRITE(p, " return mat4(u * v[0], u * v[1], u * v[2], u * v[3]);\n"); - WRITE(p, "}\n"); - } - - WRITE(p, "struct Tess {\n"); - WRITE(p, " vec3 pos;\n"); - if (doTexture) - WRITE(p, " vec2 tex;\n"); - WRITE(p, " vec4 col;\n"); - if (hasNormalTess) - WRITE(p, " vec3 nrm;\n"); - WRITE(p, "};\n"); - - WRITE(p, "void tessellate(out Tess tess) {\n"); - WRITE(p, " ivec2 point_pos = ivec2(position.z, normal.z)%s;\n", doBezier ? " * 3" : ""); - WRITE(p, " ivec2 weight_idx = ivec2(position.xy);\n"); - - // Load 4x4 control points - WRITE(p, " vec3 _pos[16];\n"); - WRITE(p, " vec2 _tex[16];\n"); - WRITE(p, " vec4 _col[16];\n"); - WRITE(p, " int index_u, index_v;\n"); - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - WRITE(p, " index_u = (%i + point_pos.x);\n", j); - WRITE(p, " index_v = (%i + point_pos.y);\n", i); - WRITE(p, " _pos[%i] = %s(u_tess_points, ivec2(index_u, index_v), 0).xyz;\n", i * 4 + j, texelFetch); - if (doTexture && hasTexcoordTess) - WRITE(p, " _tex[%i] = %s(u_tess_points, ivec2(index_u + u_spline_counts, index_v), 0).xy;\n", i * 4 + j, texelFetch); - if (hasColorTess) - WRITE(p, " _col[%i] = %s(u_tess_points, ivec2(index_u + u_spline_counts * 2, index_v), 0).rgba;\n", i * 4 + j, texelFetch); - } - } - - // Basis polynomials as weight coefficients - WRITE(p, " vec4 basis_u = %s(u_tess_weights_u, %s, 0);\n", texelFetch, "ivec2(weight_idx.x * 2, 0)"); - WRITE(p, " vec4 basis_v = %s(u_tess_weights_v, %s, 0);\n", texelFetch, "ivec2(weight_idx.y * 2, 0)"); - WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n"); - - // Tessellate - WRITE(p, " tess.pos = tess_sample(_pos, basis);\n"); - if (doTexture) { - if (hasTexcoordTess) - WRITE(p, " tess.tex = tess_sample(_tex, basis);\n"); - else - WRITE(p, " tess.tex = normal.xy;\n"); - } - if (hasColorTess) - WRITE(p, " tess.col = tess_sample(_col, basis);\n"); - else - WRITE(p, " tess.col = u_matambientalpha;\n"); - if (hasNormalTess) { - // Derivatives as weight coefficients - WRITE(p, " vec4 deriv_u = %s(u_tess_weights_u, %s, 0);\n", texelFetch, "ivec2(weight_idx.x * 2 + 1, 0)"); - WRITE(p, " vec4 deriv_v = %s(u_tess_weights_v, %s, 0);\n", texelFetch, "ivec2(weight_idx.y * 2 + 1, 0)"); - - WRITE(p, " vec3 du = tess_sample(_pos, outerProduct(deriv_u, basis_v));\n"); - WRITE(p, " vec3 dv = tess_sample(_pos, outerProduct(basis_u, deriv_v));\n"); - WRITE(p, " tess.nrm = normalize(cross(du, dv));\n"); - } - WRITE(p, "}\n"); - } - - WRITE(p, "void main() {\n"); - - if (!useHWTransform) { - // Simple pass-through of vertex data to fragment shader - if (doTexture) { - if (texcoordVec3In) { - WRITE(p, " v_texcoord = texcoord;\n"); - } else { - WRITE(p, " v_texcoord = vec3(texcoord, 1.0);\n"); - } - } - if (hasColor) { - WRITE(p, " v_color0 = color0;\n"); - if (lmode) - WRITE(p, " v_color1 = color1;\n"); - } else { - WRITE(p, " v_color0 = u_matambientalpha;\n"); - if (lmode) - WRITE(p, " v_color1 = vec3(0.0);\n"); - } - if (enableFog) { - WRITE(p, " v_fogdepth = position.w;\n"); - } - if (isModeThrough) { - WRITE(p, " vec4 outPos = u_proj_through * vec4(position.xyz, 1.0);\n"); - } else { - // The viewport is used in this case, so need to compensate for that. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " vec4 outPos = depthRoundZVP(u_proj * vec4(position.xyz, 1.0));\n"); - } else { - WRITE(p, " vec4 outPos = u_proj * vec4(position.xyz, 1.0);\n"); - } - } - WRITE(p, " gl_Position = outPos;\n"); - } else { - // Step 1: World Transform / Skinning - if (!enableBones) { - if (doBezier || doSpline) { - // Hardware tessellation - WRITE(p, " Tess tess;\n"); - WRITE(p, " tessellate(tess);\n"); - - WRITE(p, " vec3 worldpos = (u_world * vec4(tess.pos.xyz, 1.0)).xyz;\n"); - if (hasNormalTess) { - WRITE(p, " mediump vec3 worldnormal = normalize((u_world * vec4(%stess.nrm, 0.0)).xyz);\n", flipNormalTess ? "-" : ""); - } else { - WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n"); - } - } else { - // 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 { - 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, " 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"); - - // Final view and projection transforms. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " vec4 outPos = depthRoundZVP(u_proj * viewPos);\n"); - } else { - WRITE(p, " vec4 outPos = u_proj * viewPos;\n"); - } - WRITE(p, " gl_Position = outPos;\n"); - - // TODO: Declare variables for dots for shade mapping if needed. - - const char *ambientStr = (matUpdate & 1) && hasColor ? "color0" : "u_matambientalpha"; - const char *diffuseStr = (matUpdate & 2) && hasColor ? "color0.rgb" : "u_matdiffuse"; - const char *specularStr = (matUpdate & 4) && hasColor ? "color0.rgb" : "u_matspecular.rgb"; - if (doBezier || doSpline) { - // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. - ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "u_matambientalpha"; - diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "u_matdiffuse"; - specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "u_matspecular.rgb"; - } - - bool diffuseIsZero = true; - bool specularIsZero = true; - bool distanceNeeded = false; - bool anySpots = false; - if (enableLighting) { - WRITE(p, " lowp vec4 lightSum0 = u_ambient * %s + vec4(u_matemissive, 0.0);\n", ambientStr); - - for (int i = 0; i < 4; i++) { - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2)); - if (doLight[i] != LIGHT_FULL) - continue; - diffuseIsZero = false; - if (comp == GE_LIGHTCOMP_BOTH) - specularIsZero = false; - if (type != GE_LIGHTTYPE_DIRECTIONAL) - distanceNeeded = true; - if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) - anySpots = true; - } - - if (!specularIsZero) { - WRITE(p, " lowp vec3 lightSum1 = vec3(0.0);\n"); - } - if (!diffuseIsZero) { - WRITE(p, " vec3 toLight;\n"); - WRITE(p, " lowp vec3 diffuse;\n"); - } - if (distanceNeeded) { - WRITE(p, " float distance;\n"); - WRITE(p, " lowp float lightScale;\n"); - } - WRITE(p, " mediump float ldot;\n"); - if (anySpots) { - WRITE(p, " lowp float angle;\n"); - } - } - - // Calculate lights if needed. If shade mapping is enabled, lights may need to be - // at least partially calculated. - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_FULL) - continue; - - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2)); - - if (type == GE_LIGHTTYPE_DIRECTIONAL) { - // We prenormalize light positions for directional lights. - WRITE(p, " toLight = u_lightpos%i;\n", i); - } else { - WRITE(p, " toLight = u_lightpos%i - worldpos;\n", i); - WRITE(p, " distance = length(toLight);\n"); - WRITE(p, " toLight /= distance;\n"); - } - - bool doSpecular = comp == GE_LIGHTCOMP_BOTH; - bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; - - WRITE(p, " ldot = dot(toLight, worldnormal);\n"); - if (poweredDiffuse) { - // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. - // Seen in Tales of the World: Radiant Mythology (#2424.) - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); - } - - const char *timesLightScale = " * lightScale"; - - // Attenuation - switch (type) { - case GE_LIGHTTYPE_DIRECTIONAL: - timesLightScale = ""; - break; - case GE_LIGHTTYPE_POINT: - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); - break; - case GE_LIGHTTYPE_SPOT: - case GE_LIGHTTYPE_UNKNOWN: - WRITE(p, " angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i); - WRITE(p, " if (angle >= u_lightangle_spotCoef%i.x) {\n", i); - WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i); - WRITE(p, " } else {\n"); - WRITE(p, " lightScale = 0.0;\n"); - WRITE(p, " }\n"); - break; - default: - // ILLEGAL - break; - } - - WRITE(p, " diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr); - if (doSpecular) { - WRITE(p, " if (ldot >= 0.0) {\n"); - WRITE(p, " ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n"); - WRITE(p, " if (u_matspecular.a <= 0.0) {\n"); - WRITE(p, " ldot = 1.0;\n"); - WRITE(p, " } else {\n"); - WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n"); - WRITE(p, " }\n"); - WRITE(p, " if (ldot > 0.0)\n"); - WRITE(p, " lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale); - WRITE(p, " }\n"); - } - WRITE(p, " lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); - } - - if (enableLighting) { - // Sum up ambient, emissive here. - if (lmode) { - WRITE(p, " v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - // v_color1 only exists when lmode = 1. - if (specularIsZero) { - WRITE(p, " v_color1 = vec3(0.0);\n"); - } else { - WRITE(p, " v_color1 = clamp(lightSum1, 0.0, 1.0);\n"); - } - } else { - if (specularIsZero) { - WRITE(p, " v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - } else { - WRITE(p, " v_color0 = clamp(clamp(lightSum0, 0.0, 1.0) + vec4(lightSum1, 0.0), 0.0, 1.0);\n"); - } - } - } else { - // Lighting doesn't affect color. - if (hasColor) { - if (doBezier || doSpline) - WRITE(p, " v_color0 = tess.col;\n"); - else - WRITE(p, " v_color0 = color0;\n"); - } else { - WRITE(p, " v_color0 = u_matambientalpha;\n"); - } - if (lmode) - WRITE(p, " v_color1 = vec3(0.0);\n"); - } - - // Step 3: UV generation - if (doTexture) { - switch (uvGenMode) { - case GE_TEXMAP_TEXTURE_COORDS: // Scale-offset. Easy. - case GE_TEXMAP_UNKNOWN: // Not sure what this is, but Riviera uses it. Treating as coords works. - if (scaleUV) { - if (hasTexcoord) { - if (doBezier || doSpline) - WRITE(p, " v_texcoord = vec3(tess.tex * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n"); - else - WRITE(p, " v_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy, 0.0);\n"); - } else { - WRITE(p, " v_texcoord = vec3(0.0);\n"); - } - } else { - if (hasTexcoord) { - WRITE(p, " v_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n"); - } else { - WRITE(p, " v_texcoord = vec3(u_uvscaleoffset.zw, 0.0);\n"); - } - } - break; - - case GE_TEXMAP_TEXTURE_MATRIX: // Projection mapping. - { - std::string temp_tc; - switch (uvProjMode) { - case GE_PROJMAP_POSITION: // Use model space XYZ as source - temp_tc = "vec4(position.xyz, 1.0)"; - break; - case GE_PROJMAP_UV: // Use unscaled UV as source - { - // prescale is false here. - if (hasTexcoord) { - temp_tc = "vec4(texcoord.xy, 0.0, 1.0)"; - } else { - temp_tc = "vec4(0.0, 0.0, 0.0, 1.0)"; - } - } - break; - case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "vec4(normalize(-normal), 1.0)" : "vec4(normalize(normal), 1.0)"; - else - temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; - break; - case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)"; - else - temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; - break; - } - // Transform by texture matrix. XYZ as we are doing projection mapping. - WRITE(p, " v_texcoord = (u_texmtx * %s).xyz * vec3(u_uvscaleoffset.xy, 1.0);\n", temp_tc.c_str()); - } - break; - - case GE_TEXMAP_ENVIRONMENT_MAP: // Shade mapping - use dots from light sources. - { - std::string lightFactor0 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls0, ls0); - std::string lightFactor1 = StringFromFormat("(length(u_lightpos%i) == 0.0 ? worldnormal.z : dot(normalize(u_lightpos%i), worldnormal))", ls1, ls1); - WRITE(p, " v_texcoord = vec3(u_uvscaleoffset.xy * vec2(1.0 + %s, 1.0 + %s) * 0.5, 1.0);\n", lightFactor0.c_str(), lightFactor1.c_str()); - } - break; - - default: - // ILLEGAL - break; - } - } - - // Compute fogdepth - if (enableFog) - WRITE(p, " v_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n"); - } - - if (!isModeThrough && gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING)) { - WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); - // Vertex range culling doesn't happen when depth is clamped, so only do this if in range. - WRITE(p, " if (u_cullRangeMin.w <= 0.0 || (projPos.z >= u_cullRangeMin.z && projPos.z <= u_cullRangeMax.z)) {\n"); - const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y || projPos.z < u_cullRangeMin.z"; - const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y || projPos.z > u_cullRangeMax.z"; - WRITE(p, " if (%s || %s) {\n", outMin, outMax); - WRITE(p, " outPos.xyzw = vec4(u_cullRangeMax.w);\n"); - WRITE(p, " }\n"); - WRITE(p, " }\n"); - } - WRITE(p, " gl_Position = outPos;\n"); - - WRITE(p, "}\n"); -} diff --git a/GPU/GLES/VertexShaderGeneratorGLES.h b/GPU/GLES/VertexShaderGeneratorGLES.h deleted file mode 100644 index 7801b2cd6f..0000000000 --- a/GPU/GLES/VertexShaderGeneratorGLES.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2012- 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/. - -#pragma once - -#include "Common/CommonTypes.h" - -// #define USE_BONE_ARRAY - -struct VShaderID; - -void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, uint64_t *uniformMask); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 5d3468b07d..8f9e2404b6 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -338,8 +338,10 @@ + + @@ -348,7 +350,6 @@ - @@ -367,17 +368,16 @@ + - - @@ -387,12 +387,10 @@ - - @@ -401,12 +399,6 @@ true true - - true - true - true - true - true true @@ -455,12 +447,6 @@ true true - - true - true - true - true - @@ -478,7 +464,6 @@ - @@ -486,13 +471,14 @@ - + + @@ -501,7 +487,6 @@ - @@ -546,10 +531,10 @@ true true + - @@ -557,7 +542,6 @@ - @@ -566,14 +550,12 @@ - - @@ -588,12 +570,6 @@ true true - - true - true - true - true - true true @@ -648,12 +624,6 @@ true true - - true - true - true - true - @@ -671,7 +641,6 @@ - @@ -680,7 +649,6 @@ - diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 04ac5cae2d..fff4ea4a51 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -66,9 +66,6 @@ DirectX9 - - DirectX9 - DirectX9 @@ -81,9 +78,6 @@ DirectX9 - - DirectX9 - Common @@ -156,9 +150,6 @@ Vulkan - - Vulkan - Vulkan @@ -180,9 +171,6 @@ Vulkan - - Vulkan - Vulkan @@ -198,15 +186,9 @@ GLES - - GLES - GLES - - GLES - GLES @@ -219,12 +201,6 @@ D3D11 - - D3D11 - - - D3D11 - D3D11 @@ -246,9 +222,6 @@ D3D11 - - Common - Software @@ -288,6 +261,15 @@ Common + + Common + + + Common + + + Common + @@ -326,9 +308,6 @@ DirectX9 - - DirectX9 - DirectX9 @@ -341,9 +320,6 @@ DirectX9 - - DirectX9 - DirectX9 @@ -428,9 +404,6 @@ Vulkan - - Vulkan - Vulkan @@ -449,9 +422,6 @@ Vulkan - - Vulkan - Vulkan @@ -470,15 +440,9 @@ GLES - - GLES - GLES - - GLES - GLES @@ -494,12 +458,6 @@ D3D11 - - D3D11 - - - D3D11 - D3D11 @@ -524,9 +482,6 @@ D3D11 - - Common - Common @@ -575,5 +530,14 @@ Common + + Common + + + Common + + + Common + \ No newline at end of file diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 603ec061cc..a6b02d96ca 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -120,8 +120,8 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_BLENDMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_BLENDFIXEDA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_BLENDFIXEDB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_MASKRGB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE }, - { GE_CMD_MASKALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE }, + { GE_CMD_MASKRGB, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_COLORWRITEMASK }, + { GE_CMD_MASKALPHA, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_COLORWRITEMASK }, { GE_CMD_ZTEST, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE }, { GE_CMD_ZTESTENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, { GE_CMD_ZWRITEDISABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE }, @@ -468,6 +468,18 @@ void GPUCommon::Reinitialize() { framebufferManager_->DestroyAllFBOs(); } +// Call at the END of the GPU implementation's DeviceLost +void GPUCommon::DeviceLost() { + framebufferManager_->DeviceLost(); + draw_ = nullptr; +} + +// Call at the start of the GPU implementation's DeviceRestore +void GPUCommon::DeviceRestore() { + draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext(); + framebufferManager_->DeviceRestore(draw_); +} + void GPUCommon::UpdateVsyncInterval(bool force) { #if !(PPSSPP_PLATFORM(ANDROID) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) || PPSSPP_PLATFORM(IOS)) int desiredVSyncInterval = g_Config.bVSync ? 1 : 0; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 77c354a43f..533f027542 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -261,6 +261,9 @@ public: } protected: + void DeviceLost() override; + void DeviceRestore() override; + void SetDrawType(DrawType type, GEPrimitiveType prim) { if (type != lastDraw_) { // We always flush when drawing splines/beziers so no need to do so here diff --git a/GPU/GPUState.h b/GPU/GPUState.h index f012e023bc..3af959f6b0 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -480,7 +480,7 @@ enum { GPU_SUPPORTS_16BIT_FORMATS = FLAG_BIT(13), GPU_SUPPORTS_DEPTH_CLAMP = FLAG_BIT(14), GPU_SUPPORTS_32BIT_INT_FSHADER = FLAG_BIT(15), - // Free bit: 16 + GPU_SUPPORTS_DEPTH_TEXTURE = FLAG_BIT(16), GPU_SUPPORTS_ACCURATE_DEPTH = FLAG_BIT(17), GPU_SUPPORTS_VAO = FLAG_BIT(18), GPU_SUPPORTS_COPY_IMAGE = FLAG_BIT(19), @@ -490,9 +490,10 @@ enum { GPU_ROUND_DEPTH_TO_16BIT = FLAG_BIT(23), // Can be disabled either per game or if we use a real 16-bit depth buffer GPU_SUPPORTS_TEXTURE_LOD_CONTROL = FLAG_BIT(24), GPU_SUPPORTS_FRAMEBUFFER_BLIT = FLAG_BIT(26), + GPU_SUPPORTS_FRAMEBUFFER_BLIT_TO_DEPTH = FLAG_BIT(27), GPU_SUPPORTS_OES_TEXTURE_NPOT = FLAG_BIT(28), GPU_NEEDS_Z_EQUAL_W_HACK = FLAG_BIT(29), - GPU_PREFER_CPU_DOWNLOAD = FLAG_BIT(30), + // Free bit: 30 GPU_PREFER_REVERSE_COLOR_ORDER = FLAG_BIT(31), }; @@ -539,9 +540,9 @@ struct GPUStateCache { Dirty(DIRTY_TEXCLAMP); } } - void SetAllowShaderBlend(bool allow) { - if (allowShaderBlend != allow) { - allowShaderBlend = allow; + void SetAllowFramebufferRead(bool allow) { + if (allowFramebufferRead != allow) { + allowFramebufferRead = allow; Dirty(DIRTY_FRAGMENTSHADER_STATE); } } @@ -563,7 +564,7 @@ struct GPUStateCache { bool bgraTexture; bool needShaderTexClamp; - bool allowShaderBlend; + bool allowFramebufferRead; float morphWeights[8]; u32 deferredVertTypeDirty; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index f5c53b7bbb..7f3547c401 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -41,7 +41,7 @@ #include "GPU/Software/TransformUnit.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/PresentationCommon.h" -#include "GPU/Common/ShaderTranslation.h" +#include "Common/GPU/ShaderTranslation.h" #include "GPU/Common/SplineCommon.h" #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" @@ -72,11 +72,11 @@ SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw) switch (GetGPUBackend()) { case GPUBackend::OPENGL: - presentation_->SetLanguage(gl_extensions.IsCoreContext ? GLSL_300 : GLSL_140); + presentation_->SetLanguage(gl_extensions.IsCoreContext ? GLSL_3xx : GLSL_1xx); break; case GPUBackend::DIRECT3D9: ShaderTranslationInit(); - presentation_->SetLanguage(HLSL_DX9); + presentation_->SetLanguage(HLSL_D3D9); break; case GPUBackend::DIRECT3D11: ShaderTranslationInit(); diff --git a/GPU/Vulkan/DebugVisVulkan.cpp b/GPU/Vulkan/DebugVisVulkan.cpp index 78616e86c5..9d1365bb5f 100644 --- a/GPU/Vulkan/DebugVisVulkan.cpp +++ b/GPU/Vulkan/DebugVisVulkan.cpp @@ -19,6 +19,8 @@ #include "Common/GPU/thin3d.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" +#include "Common/System/Display.h" +#include "Common/System/System.h" #include "DebugVisVulkan.h" #include "Common/GPU/Vulkan/VulkanMemory.h" @@ -98,17 +100,15 @@ void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu) { iter->Release(); } -void DrawProfilerVis(UIContext *ui, GPUInterface *gpu) { +void DrawGPUProfilerVis(UIContext *ui, GPUInterface *gpu) { if (!gpu) { return; } using namespace Draw; - const int padding = 10; - const int columnWidth = 256; - const int starty = padding * 8; + const int padding = 10 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT); + const int starty = 50 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP); int x = padding; int y = starty; - int w = columnWidth; // We will double this when actually drawing to make the pixels visible. ui->Begin(); @@ -118,7 +118,7 @@ void DrawProfilerVis(UIContext *ui, GPUInterface *gpu) { Draw::DrawContext *draw = ui->GetDrawContext(); ui->SetFontScale(0.4f, 0.4f); - ui->DrawTextShadow(text.c_str(), 10, 50, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + ui->DrawTextShadow(text.c_str(), x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); ui->SetFontScale(1.0f, 1.0f); ui->Flush(); } diff --git a/GPU/Vulkan/DebugVisVulkan.h b/GPU/Vulkan/DebugVisVulkan.h index b616d856f3..210771412a 100644 --- a/GPU/Vulkan/DebugVisVulkan.h +++ b/GPU/Vulkan/DebugVisVulkan.h @@ -24,4 +24,4 @@ class UIContext; // gpu MUST be an instance of GPU_Vulkan. If not, will definitely crash. void DrawAllocatorVis(UIContext *ui, GPUInterface *gpu); -void DrawProfilerVis(UIContext *ui, GPUInterface *gpu); +void DrawGPUProfilerVis(UIContext *ui, GPUInterface *gpu); diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 04d48db348..b2f86d23fd 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -439,18 +439,14 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView _assert_msg_(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, which is fine. - VkWriteDescriptorSet writes[7]{}; + VkWriteDescriptorSet writes[9]{}; // Main texture int n = 0; VkDescriptorImageInfo tex[3]{}; if (imageView) { _dbg_assert_(sampler != VK_NULL_HANDLE); -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - tex[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; -#else tex[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -#endif tex[0].imageView = imageView; tex[0].sampler = sampler; writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -464,11 +460,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView } if (boundSecondary_) { -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - tex[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL; -#else tex[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -#endif tex[1].imageView = boundSecondary_; tex[1].sampler = samplerSecondary_; writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -482,11 +474,7 @@ VkDescriptorSet DrawEngineVulkan::GetOrCreateDescriptorSet(VkImageView imageView } 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; @@ -851,7 +839,7 @@ void DrawEngineVulkan::DoFlush() { lastRenderStepId_ = curRenderStepId; } - renderManager->BindPipeline(pipeline->pipeline); + renderManager->BindPipeline(pipeline->pipeline, (PipelineFlags)pipeline->flags); if (pipeline != lastPipeline_) { if (lastPipeline_ && !(lastPipeline_->UsesBlendConstant() && pipeline->UsesBlendConstant())) { gstate_c.Dirty(DIRTY_BLEND_STATE); @@ -976,7 +964,7 @@ void DrawEngineVulkan::DoFlush() { lastRenderStepId_ = curRenderStepId; } - renderManager->BindPipeline(pipeline->pipeline); + renderManager->BindPipeline(pipeline->pipeline, (PipelineFlags)pipeline->flags); if (pipeline != lastPipeline_) { if (lastPipeline_ && !lastPipeline_->UsesBlendConstant() && pipeline->UsesBlendConstant()) { gstate_c.Dirty(DIRTY_BLEND_STATE); diff --git a/GPU/Vulkan/DrawEngineVulkan.h b/GPU/Vulkan/DrawEngineVulkan.h index 3f46a62186..0e36cbcc16 100644 --- a/GPU/Vulkan/DrawEngineVulkan.h +++ b/GPU/Vulkan/DrawEngineVulkan.h @@ -199,7 +199,7 @@ private: void ApplyDrawStateLate(VulkanRenderManager *renderManager, bool applyStencilRef, uint8_t stencilRef, bool useBlendConstant); void ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManager, ShaderManagerVulkan *shaderManager, int prim, VulkanPipelineRasterStateKey &key, VulkanDynamicState &dynState); void BindShaderBlendTex(); - void ResetShaderBlending(); + void ResetFramebufferRead(); void InitDeviceObjects(); void DestroyDeviceObjects(); diff --git a/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp b/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp deleted file mode 100644 index 25a96b9761..0000000000 --- a/GPU/Vulkan/FragmentShaderGeneratorVulkan.cpp +++ /dev/null @@ -1,602 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include -#include - -#include "Common/GPU/OpenGL/GLFeatures.h" - -#include "Common/Log.h" -#include "Common/StringUtils.h" -#include "Core/Reporting.h" -#include "Core/Config.h" -#include "Core/System.h" -#include "GPU/Common/GPUStateUtils.h" -#include "GPU/Common/ShaderId.h" -#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" -#include "GPU/Vulkan/FramebufferManagerVulkan.h" -#include "GPU/Vulkan/ShaderManagerVulkan.h" -#include "GPU/Vulkan/PipelineManagerVulkan.h" - -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" - -static const char *vulkan_glsl_preamble = - "#version 450\n" - "#extension GL_ARB_separate_shader_objects : enable\n" - "#extension GL_ARB_shading_language_420pack : enable\n" - "#extension GL_ARB_conservative_depth : enable\n" - "#extension GL_ARB_shader_image_load_store : enable\n\n"; - -#define WRITE p+=sprintf - -// Missing: Z depth range -bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer, uint32_t vulkanVendorId) { - char *p = buffer; - - const char *lastFragData = nullptr; - - WRITE(p, "%s", vulkan_glsl_preamble); - - bool lmode = id.Bit(FS_BIT_LMODE); - bool doTexture = id.Bit(FS_BIT_DO_TEXTURE); - bool enableFog = id.Bit(FS_BIT_ENABLE_FOG); - bool enableAlphaTest = id.Bit(FS_BIT_ALPHA_TEST); - - bool alphaTestAgainstZero = id.Bit(FS_BIT_ALPHA_AGAINST_ZERO); - bool testForceToZero = id.Bit(FS_BIT_TEST_DISCARD_TO_ZERO); - bool enableColorTest = id.Bit(FS_BIT_COLOR_TEST); - bool colorTestAgainstZero = id.Bit(FS_BIT_COLOR_AGAINST_ZERO); - bool enableColorDoubling = id.Bit(FS_BIT_COLOR_DOUBLE); - 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); - bool needShaderTexClamp = id.Bit(FS_BIT_SHADER_TEX_CLAMP); - - GETexFunc texFunc = (GETexFunc)id.Bits(FS_BIT_TEXFUNC, 3); - bool textureAtOffset = id.Bit(FS_BIT_TEXTURE_AT_OFFSET); - - ReplaceBlendType replaceBlend = static_cast(id.Bits(FS_BIT_REPLACE_BLEND, 3)); - ReplaceAlphaType stencilToAlpha = static_cast(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)); - - GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); - GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); - GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); - StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4); - - bool isModeClear = id.Bit(FS_BIT_CLEARMODE); - - const char *shading = doFlatShading ? "flat" : ""; - bool earlyFragmentTests = ((!enableAlphaTest && !enableColorTest) || testForceToZero) && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); - bool useAdrenoBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL); - - if (earlyFragmentTests) { - WRITE(p, "layout (early_fragment_tests) in;\n"); - } else if (useAdrenoBugWorkaround && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { - WRITE(p, "layout (depth_unchanged) out float gl_FragDepth;\n"); - } - - 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"); - } - - if (!isModeClear && replaceBlend > REPLACE_BLEND_STANDARD) { - if (replaceBlend == REPLACE_BLEND_COPY_FBO) { - WRITE(p, "layout (binding = 1) uniform sampler2D fbotex;\n"); - } - } - - 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); - if (enableFog) { - WRITE(p, "layout (location = 3) in float v_fogdepth;\n"); - } - if (doTexture) { - WRITE(p, "layout (location = 0) in vec3 v_texcoord;\n"); - } - - if (enableAlphaTest && !alphaTestAgainstZero) { - WRITE(p, "int roundAndScaleTo255i(in float x) { return int(floor(x * 255.0 + 0.5)); }\n"); - } - if (enableColorTest && !colorTestAgainstZero) { - WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n"); - } - - WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n"); - if (stencilToAlpha == REPLACE_ALPHA_DUALSOURCE) { - WRITE(p, "layout (location = 0, index = 1) out vec4 fragColor1;\n"); - } - - // PowerVR needs a custom modulo function. For some reason, this has far higher precision than the builtin one. - if ((gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) && needShaderTexClamp) { - WRITE(p, "float mymod(float a, float b) { return a - b * floor(a / b); }\n"); - } - - WRITE(p, "void main() {\n"); - if (isModeClear) { - // Clear mode does not allow any fancy shading. - WRITE(p, " vec4 v = v_color0;\n"); - } else { - const char *secondary = ""; - // Secondary color for specular on top of texture - if (lmode) { - WRITE(p, " vec4 s = vec4(v_color1, 0.0);\n"); - secondary = " + s"; - } else { - secondary = ""; - } - - if (doTexture) { - const char *texcoord = "v_texcoord"; - // TODO: Not sure the right way to do this for projection. - // This path destroys resolution on older PowerVR no matter what I do, so we disable it on SGX 540 and lesser, and live with the consequences. - if (needShaderTexClamp && !(gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_TERRIBLE)) { - // We may be clamping inside a larger surface (tex = 64x64, buffer=480x272). - // We may also be wrapping in such a surface, or either one in a too-small surface. - // Obviously, clamping to a smaller surface won't work. But better to clamp to something. - std::string ucoord = "v_texcoord.x"; - std::string vcoord = "v_texcoord.y"; - if (doTextureProjection) { - ucoord = "(v_texcoord.x / v_texcoord.z)"; - vcoord = "(v_texcoord.y / v_texcoord.z)"; - } - - std::string modulo = (gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) ? "mymod" : "mod"; - - if (id.Bit(FS_BIT_CLAMP_S)) { - ucoord = "clamp(" + ucoord + ", base.texclamp.z, base.texclamp.x - base.texclamp.z)"; - } else { - ucoord = modulo + "(" + ucoord + ", base.texclamp.x)"; - } - if (id.Bit(FS_BIT_CLAMP_T)) { - vcoord = "clamp(" + vcoord + ", base.texclamp.w, base.texclamp.y - base.texclamp.w)"; - } else { - vcoord = modulo + "(" + vcoord + ", base.texclamp.y)"; - } - if (textureAtOffset) { - ucoord = "(" + ucoord + " + base.texclampoff.x)"; - vcoord = "(" + vcoord + " + base.texclampoff.y)"; - } - - WRITE(p, " vec2 fixedcoord = vec2(%s, %s);\n", ucoord.c_str(), vcoord.c_str()); - texcoord = "fixedcoord"; - // We already projected it. - doTextureProjection = false; - } - - if (!shaderDepal) { - if (doTextureProjection) { - WRITE(p, " vec4 t = textureProj(tex, %s);\n", texcoord); - } else { - WRITE(p, " vec4 t = texture(tex, %s.xy);\n", texcoord); - } - } else { - if (doTextureProjection) { - // We don't use textureProj because we need to manually offset from the divided coordinate to do filtering here. - // On older hardware it has the advantage of higher resolution math, but such old hardware can't run Vulkan. - WRITE(p, " vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord); - } else { - WRITE(p, " vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord); - } - WRITE(p, " vec2 tsize = textureSize(tex, 0);\n"); - WRITE(p, " vec2 fraction;\n"); - WRITE(p, " bool bilinear = (base.depal_mask_shift_off_fmt >> 31) != 0;\n"); - WRITE(p, " if (bilinear) {\n"); - WRITE(p, " uv_round = uv * tsize - vec2(0.5, 0.5);\n"); - WRITE(p, " fraction = fract(uv_round);\n"); - WRITE(p, " uv_round = (uv_round - fraction + vec2(0.5, 0.5)) / tsize;\n"); // We want to take our four point samples at pixel centers. - WRITE(p, " } else {\n"); - WRITE(p, " uv_round = uv;\n"); - WRITE(p, " }\n"); - WRITE(p, " vec4 t = texture(tex, uv_round);\n"); - WRITE(p, " vec4 t1 = textureOffset(tex, uv_round, ivec2(1, 0));\n"); - WRITE(p, " vec4 t2 = textureOffset(tex, uv_round, ivec2(0, 1));\n"); - WRITE(p, " vec4 t3 = textureOffset(tex, uv_round, ivec2(1, 1));\n"); - 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, " 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 * 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 * 15.99);\n"); - WRITE(p, " index1 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); - WRITE(p, " col = uvec4(t2.rgba * 15.99);\n"); - WRITE(p, " index2 = (col.a << 12) | (col.b << 8) | (col.g << 4) | (col.r);\n"); - WRITE(p, " col = uvec4(t3.rgba * 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 * 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 * 255.99);\n"); - WRITE(p, " index1 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); - WRITE(p, " col = uvec4(t2.rgba * 255.99);\n"); - WRITE(p, " index2 = (col.a << 24) | (col.b << 16) | (col.g << 8) | (col.r);\n"); - WRITE(p, " col = uvec4(t3.rgba * 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 && !(index0 == index1 && index1 == index2 && index2 == index3)) {\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) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " vec4 v = p * t%s;\n", secondary); - break; - - case GE_TEXFUNC_DECAL: - WRITE(p, " vec4 v = vec4(mix(p.rgb, t.rgb, t.a), p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_BLEND: - WRITE(p, " vec4 v = vec4(mix(p.rgb, base.texenv.rgb, t.rgb), p.a * t.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_REPLACE: - WRITE(p, " vec4 v = t%s;\n", secondary); - break; - - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a * t.a)%s;\n", secondary); - break; - default: - WRITE(p, " vec4 v = p;\n"); break; - } - } else { // texfmt == RGB - switch (texFunc) { - case GE_TEXFUNC_MODULATE: - WRITE(p, " vec4 v = vec4(t.rgb * p.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_DECAL: - WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_BLEND: - WRITE(p, " vec4 v = vec4(mix(p.rgb, base.texenv.rgb, t.rgb), p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_REPLACE: - WRITE(p, " vec4 v = vec4(t.rgb, p.a)%s;\n", secondary); - break; - - case GE_TEXFUNC_ADD: - case GE_TEXFUNC_UNKNOWN1: - case GE_TEXFUNC_UNKNOWN2: - case GE_TEXFUNC_UNKNOWN3: - WRITE(p, " vec4 v = vec4(p.rgb + t.rgb, p.a)%s;\n", secondary); break; - default: - WRITE(p, " vec4 v = p;\n"); break; - } - } - - if (enableColorDoubling) { - // This happens before fog is applied. - WRITE(p, " v.rgb = clamp(v.rgb * 2.0, 0.0, 1.0);\n"); - } - } else { - // No texture mapping - WRITE(p, " vec4 v = v_color0 %s;\n", secondary); - } - - // Texture access is at half texels [0.5/256, 255.5/256], but colors are normalized [0, 255]. - // So we have to scale to account for the difference. - std::string alphaTestXCoord = "0"; - - const char *discardStatement = testForceToZero ? "v.a = 0.0;" : "discard;"; - if (enableAlphaTest) { - if (alphaTestAgainstZero) { - // When testing against 0 (extremely common), we can avoid some math. - // 0.002 is approximately half of 1.0 / 255.0. - if (alphaTestFunc == GE_COMP_NOTEQUAL || alphaTestFunc == GE_COMP_GREATER) { - WRITE(p, " if (v.a < 0.002) %s\n", discardStatement); - } else if (alphaTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. Happens sometimes, actually... - WRITE(p, " if (v.a > 0.002) %s\n", discardStatement); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, " %s\n", discardStatement); - } - } else { - const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " }; - if (alphaTestFuncs[alphaTestFunc][0] != '#') { - WRITE(p, " if ((roundAndScaleTo255i(v.a) & base.alphacolormask.a) %s base.alphacolorref.a) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement); - } else { - // This means NEVER. See above. - WRITE(p, " %s\n", discardStatement); - } - } - } - - if (enableFog) { - WRITE(p, " float fogCoef = clamp(v_fogdepth, 0.0, 1.0);\n"); - WRITE(p, " v = mix(vec4(base.fogcolor, v.a), v, fogCoef);\n"); - // WRITE(p, " v.x = v_depth;\n"); - } - - if (enableColorTest) { - if (colorTestAgainstZero) { - // When testing against 0 (common), we can avoid some math. - // Have my doubts that this special case is actually worth it, but whatever. - // 0.002 is approximately half of 1.0 / 255.0. - if (colorTestFunc == GE_COMP_NOTEQUAL) { - WRITE(p, " if (v.r + v.g + v.b < 0.002) %s\n", discardStatement); - } else if (colorTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. - WRITE(p, " if (v.r + v.g + v.b > 0.002) %s\n", discardStatement); - } else { - // NEVER has been logged as used by games, although it makes little sense - statically failing. - // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. - WRITE(p, " %s\n", discardStatement); - } - } else { - const char *colorTestFuncs[] = { "#", "#", " != ", " == " }; - if (colorTestFuncs[colorTestFunc][0] != '#') { - WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n"); - WRITE(p, " if ((v_scaled & base.alphacolormask.rgb) %s (base.alphacolorref.rgb & base.alphacolormask.rgb)) %s\n", colorTestFuncs[colorTestFunc], discardStatement); - } else { - WRITE(p, " %s\n", discardStatement); - } - } - } - - if (replaceBlend == REPLACE_BLEND_2X_SRC) { - WRITE(p, " v.rgb = v.rgb * 2.0;\n"); - } - - if (replaceBlend == REPLACE_BLEND_PRE_SRC || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - const char *srcFactor = "ERROR"; - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "ERROR"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "ERROR"; break; - case GE_SRCBLEND_FIXA: srcFactor = "base.blendFixA"; break; - } - - WRITE(p, " v.rgb = v.rgb * %s;\n", srcFactor); - } - - if (replaceBlend == REPLACE_BLEND_COPY_FBO) { - WRITE(p, " lowp vec4 destColor = texelFetch(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n"); - - const char *srcFactor = "vec3(1.0)"; - const char *dstFactor = "vec3(0.0)"; - - switch (replaceBlendFuncA) { - case GE_SRCBLEND_DSTCOLOR: srcFactor = "destColor.rgb"; break; - case GE_SRCBLEND_INVDSTCOLOR: srcFactor = "(vec3(1.0) - destColor.rgb)"; break; - case GE_SRCBLEND_SRCALPHA: srcFactor = "vec3(v.a)"; break; - case GE_SRCBLEND_INVSRCALPHA: srcFactor = "vec3(1.0 - v.a)"; break; - case GE_SRCBLEND_DSTALPHA: srcFactor = "vec3(destColor.a)"; break; - case GE_SRCBLEND_INVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a)"; break; - case GE_SRCBLEND_DOUBLESRCALPHA: srcFactor = "vec3(v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVSRCALPHA: srcFactor = "vec3(1.0 - v.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEDSTALPHA: srcFactor = "vec3(destColor.a * 2.0)"; break; - case GE_SRCBLEND_DOUBLEINVDSTALPHA: srcFactor = "vec3(1.0 - destColor.a * 2.0)"; break; - case GE_SRCBLEND_FIXA: srcFactor = "base.blendFixA"; break; - } - switch (replaceBlendFuncB) { - case GE_DSTBLEND_SRCCOLOR: dstFactor = "v.rgb"; break; - case GE_DSTBLEND_INVSRCCOLOR: dstFactor = "(vec3(1.0) - v.rgb)"; break; - case GE_DSTBLEND_SRCALPHA: dstFactor = "vec3(v.a)"; break; - case GE_DSTBLEND_INVSRCALPHA: dstFactor = "vec3(1.0 - v.a)"; break; - case GE_DSTBLEND_DSTALPHA: dstFactor = "vec3(destColor.a)"; break; - case GE_DSTBLEND_INVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a)"; break; - case GE_DSTBLEND_DOUBLESRCALPHA: dstFactor = "vec3(v.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEINVSRCALPHA: dstFactor = "vec3(1.0 - v.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEDSTALPHA: dstFactor = "vec3(destColor.a * 2.0)"; break; - case GE_DSTBLEND_DOUBLEINVDSTALPHA: dstFactor = "vec3(1.0 - destColor.a * 2.0)"; break; - case GE_DSTBLEND_FIXB: dstFactor = "base.blendFixB"; break; - } - - switch (replaceBlendEq) { - case GE_BLENDMODE_MUL_AND_ADD: - WRITE(p, " v.rgb = v.rgb * %s + destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT: - WRITE(p, " v.rgb = v.rgb * %s - destColor.rgb * %s;\n", srcFactor, dstFactor); - break; - case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: - WRITE(p, " v.rgb = destColor.rgb * %s - v.rgb * %s;\n", dstFactor, srcFactor); - break; - case GE_BLENDMODE_MIN: - WRITE(p, " v.rgb = min(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_MAX: - WRITE(p, " v.rgb = max(v.rgb, destColor.rgb);\n"); - break; - case GE_BLENDMODE_ABSDIFF: - WRITE(p, " v.rgb = abs(v.rgb - destColor.rgb);\n"); - break; - } - } - - if (replaceBlend == REPLACE_BLEND_2X_ALPHA || replaceBlend == REPLACE_BLEND_PRE_SRC_2X_ALPHA) { - WRITE(p, " v.a = v.a * 2.0;\n"); - } - } - - std::string replacedAlpha = "0.0"; - char replacedAlphaTemp[64] = ""; - if (stencilToAlpha != REPLACE_ALPHA_NO) { - switch (replaceAlphaWithStencilType) { - case STENCIL_VALUE_UNIFORM: - replacedAlpha = "base.stencilReplace"; - break; - - case STENCIL_VALUE_ZERO: - replacedAlpha = "0.0"; - break; - - case STENCIL_VALUE_ONE: - case STENCIL_VALUE_INVERT: - // In invert, we subtract by one, but we want to output one here. - replacedAlpha = "1.0"; - break; - - case STENCIL_VALUE_INCR_4: - case STENCIL_VALUE_DECR_4: - // We're adding/subtracting, just by the smallest value in 4-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 15.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_INCR_8: - case STENCIL_VALUE_DECR_8: - // We're adding/subtracting, just by the smallest value in 8-bit. - snprintf(replacedAlphaTemp, sizeof(replacedAlphaTemp), "%f", 1.0 / 255.0); - replacedAlpha = replacedAlphaTemp; - break; - - case STENCIL_VALUE_KEEP: - // Do nothing. We'll mask out the alpha using color mask. - break; - } - } - - switch (stencilToAlpha) { - case REPLACE_ALPHA_DUALSOURCE: - WRITE(p, " fragColor0 = vec4(v.rgb, %s);\n", replacedAlpha.c_str()); - WRITE(p, " fragColor1 = vec4(0.0, 0.0, 0.0, v.a);\n"); - break; - - case REPLACE_ALPHA_YES: - WRITE(p, " fragColor0 = vec4(v.rgb, %s);\n", replacedAlpha.c_str()); - break; - - case REPLACE_ALPHA_NO: - WRITE(p, " fragColor0 = v;\n"); - break; - - default: - ERROR_LOG(G3D, "Bad stencil-to-alpha type, corrupt ID?"); - return false; - } - - LogicOpReplaceType replaceLogicOpType = (LogicOpReplaceType)id.Bits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2); - switch (replaceLogicOpType) { - case LOGICOPTYPE_ONE: - WRITE(p, " fragColor0.rgb = vec3(1.0, 1.0, 1.0);\n"); - break; - case LOGICOPTYPE_INVERT: - WRITE(p, " fragColor0.rgb = vec3(1.0, 1.0, 1.0) - fragColor0.rgb;\n"); - break; - case LOGICOPTYPE_NORMAL: - break; - - default: - ERROR_LOG(G3D, "Bad logic op type, corrupt ID?"); - return false; - } - - if (gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { - const double scale = DepthSliceFactor() * 65535.0; - - WRITE(p, " highp float z = gl_FragCoord.z;\n"); - if (gstate_c.Supports(GPU_SUPPORTS_ACCURATE_DEPTH)) { - // We center the depth with an offset, but only its fraction matters. - // When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0. - if (((int)(DepthSliceFactor() - 1.0f) & 1) == 1) { - WRITE(p, " z = (floor((z * %f) - (1.0 / 2.0)) + (1.0 / 2.0)) * (1.0 / %f);\n", scale, scale); - } else { - WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale); - } - } else { - WRITE(p, " z = (1.0/65535.0) * floor(z * 65535.0);\n"); - } - if (PSP_CoreParameter().compat.flags().DecreaseFragmentDepthAccuracy) { - WRITE(p, " z /= 2000.0;\n"); // Nasty hack to completely remove flicker in Heroes Phantasia - } - WRITE(p, " gl_FragDepth = z;\n"); - } else if (!earlyFragmentTests && useAdrenoBugWorkaround) { - // Adreno (and possibly MESA/others) apply early frag tests even with discard in the shader. - // Writing depth prevents the bug, even with depth_unchanged specified. - WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n"); - } - - WRITE(p, "}\n"); - - return true; -} diff --git a/GPU/Vulkan/FragmentShaderGeneratorVulkan.h b/GPU/Vulkan/FragmentShaderGeneratorVulkan.h deleted file mode 100644 index 7fb02edc62..0000000000 --- a/GPU/Vulkan/FragmentShaderGeneratorVulkan.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -// Copyright (c) 2012- 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/. - -#pragma once - -struct FShaderID; - -bool GenerateVulkanGLSLFragmentShader(const FShaderID &id, char *buffer, uint32_t vulkanVendorId); diff --git a/GPU/Vulkan/FramebufferManagerVulkan.cpp b/GPU/Vulkan/FramebufferManagerVulkan.cpp index 399aa3cd87..c293c1b5b9 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.cpp +++ b/GPU/Vulkan/FramebufferManagerVulkan.cpp @@ -225,7 +225,7 @@ void FramebufferManagerVulkan::DrawActiveTexture(float x, float y, float w, floa VkDescriptorSet descSet = vulkan2D_->GetDescriptorSet(view, (flags & DRAWTEX_LINEAR) ? linearSampler_ : nearestSampler_, VK_NULL_HANDLE, VK_NULL_HANDLE); VkBuffer vbuffer; VkDeviceSize offset = push_->Push(vtx, sizeof(vtx), &vbuffer); - renderManager->BindPipeline(cur2DPipeline_); + renderManager->BindPipeline(cur2DPipeline_, (PipelineFlags)0); renderManager->Draw(vulkan2D_->GetPipelineLayout(), descSet, 0, nullptr, vbuffer, offset, 4); } @@ -242,69 +242,6 @@ int FramebufferManagerVulkan::GetLineWidth() { } } -// This also binds vfb as the current render target. -void FramebufferManagerVulkan::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) { - if (!useBufferedRendering_ || !vfb->fbo) { - return; - } - - // Technically, we should at this point re-interpret the bytes of the old format to the new. - // That might get tricky, and could cause unnecessary slowness in some games. - // For now, we just clear alpha/stencil from 565, which fixes shadow issues in Kingdom Hearts. - // (it uses 565 to write zeros to the buffer, then 4444 to actually render the shadow.) - // - // The best way to do this may ultimately be to create a new FBO (combine with any resize?) - // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex - // to exactly reproduce in 4444 and 8888 formats. - - if (old == GE_FORMAT_565) { - // We have to bind here instead of clear, since it can be that no framebuffer is bound. - // The backend can sometimes directly optimize it to a clear. - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "ReformatFramebuffer"); - // draw_->Clear(Draw::FBChannel::FB_COLOR_BIT | Draw::FBChannel::FB_STENCIL_BIT, 0, 0.0f, 0); - - // Need to dirty anything that has command buffer dynamic state, in case we started a new pass above. - // Should find a way to feed that information back, maybe... Or simply correct the issue in the rendermanager. - gstate_c.Dirty(DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE); - } -} - -VkImageView FramebufferManagerVulkan::BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags) { - if (!framebuffer->fbo || !useBufferedRendering_) { - gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE; - return VK_NULL_HANDLE; - } - - // currentRenderVfb_ will always be set when this is called, except from the GE debugger. - // Let's just not bother with the copy in that case. - bool skipCopy = (flags & BINDFBCOLOR_MAY_COPY) == 0; - if (GPUStepping::IsStepping()) { - skipCopy = true; - } - // Currently rendering to this framebuffer. Need to make a copy. - if (!skipCopy && framebuffer == currentRenderVfb_) { - // TODO: Maybe merge with bvfbs_? Not sure if those could be packing, and they're created at a different size. - Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight, (Draw::FBColorDepth)framebuffer->colorDepth); - if (renderCopy) { - VirtualFramebuffer copyInfo = *framebuffer; - copyInfo.fbo = renderCopy; - CopyFramebufferForColorTexture(©Info, framebuffer, flags); - RebindFramebuffer("RebindFramebuffer - BindFramebufferAsColorTexture"); - draw_->BindFramebufferAsTexture(renderCopy, stage, Draw::FB_COLOR_BIT, 0); - } else { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - } - return (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE0_IMAGEVIEW); - } else if (framebuffer != currentRenderVfb_ || (flags & BINDFBCOLOR_FORCE_SELF) != 0) { - draw_->BindFramebufferAsTexture(framebuffer->fbo, stage, Draw::FB_COLOR_BIT, 0); - return (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE0_IMAGEVIEW); - } else { - ERROR_LOG_REPORT_ONCE(vulkanSelfTexture, G3D, "Attempting to texture from target (src=%08x / target=%08x / flags=%d)", framebuffer->fb_address, currentRenderVfb_->fb_address, flags); - // To do this safely in Vulkan, we need to use input attachments. - return VK_NULL_HANDLE; - } -} - void FramebufferManagerVulkan::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) { // Nothing to do here. } @@ -339,8 +276,10 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX return; } - float srcXFactor = (float)src->renderWidth / (float)src->bufferWidth; - float srcYFactor = (float)src->renderHeight / (float)src->bufferHeight; + float srcXFactor = (float)src->renderScaleFactor; + float srcYFactor = (float)src->renderScaleFactor; + + // Some games use wrong-format block transfers. Simulate that. const int srcBpp = src->format == GE_FORMAT_8888 ? 4 : 2; if (srcBpp != bpp && bpp != 0) { srcXFactor = (srcXFactor * bpp) / srcBpp; @@ -350,8 +289,8 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX int srcY1 = srcY * srcYFactor; int srcY2 = (srcY + h) * srcYFactor; - float dstXFactor = (float)dst->renderWidth / (float)dst->bufferWidth; - float dstYFactor = (float)dst->renderHeight / (float)dst->bufferHeight; + float dstXFactor = (float)dst->renderScaleFactor; + float dstYFactor = (float)dst->renderScaleFactor; const int dstBpp = dst->format == GE_FORMAT_8888 ? 4 : 2; if (dstBpp != bpp && bpp != 0) { dstXFactor = (dstXFactor * bpp) / dstBpp; @@ -367,15 +306,12 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX return; } - // BlitFramebuffer can clip, but CopyFramebufferImage is more restricted. - // In case the src goes outside, we just skip the optimization in that case. const bool sameSize = dstX2 - dstX1 == srcX2 - srcX1 && dstY2 - dstY1 == srcY2 - srcY1; - const bool sameDepth = dst->colorDepth == src->colorDepth; const bool srcInsideBounds = srcX2 <= src->renderWidth && srcY2 <= src->renderHeight; const bool dstInsideBounds = dstX2 <= dst->renderWidth && dstY2 <= dst->renderHeight; const bool xOverlap = src == dst && srcX2 > dstX1 && srcX1 < dstX2; const bool yOverlap = src == dst && srcY2 > dstY1 && srcY1 < dstY2; - if (sameSize && sameDepth && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { + if (sameSize && srcInsideBounds && dstInsideBounds && !(xOverlap && yOverlap)) { draw_->CopyFramebufferImage(src->fbo, 0, srcX1, srcY1, 0, dst->fbo, 0, dstX1, dstY1, 0, dstX2 - dstX1, dstY2 - dstY1, 1, Draw::FB_COLOR_BIT, "BlitFramebuffer_Copy"); } else { draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST, "BlitFramebuffer_Blit"); @@ -390,15 +326,12 @@ void FramebufferManagerVulkan::EndFrame() { } void FramebufferManagerVulkan::DeviceLost() { - DestroyAllFBOs(); + FramebufferManagerCommon::DeviceLost(); DestroyDeviceObjects(); - presentation_->DeviceLost(); } -void FramebufferManagerVulkan::DeviceRestore(VulkanContext *vulkan, Draw::DrawContext *draw) { - vulkan_ = vulkan; - draw_ = draw; - presentation_->DeviceRestore(draw); - +void FramebufferManagerVulkan::DeviceRestore(Draw::DrawContext *draw) { + FramebufferManagerCommon::DeviceRestore(draw); + vulkan_ = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); InitDeviceObjects(); } diff --git a/GPU/Vulkan/FramebufferManagerVulkan.h b/GPU/Vulkan/FramebufferManagerVulkan.h index a653aeb15d..d8b20e3826 100644 --- a/GPU/Vulkan/FramebufferManagerVulkan.h +++ b/GPU/Vulkan/FramebufferManagerVulkan.h @@ -49,15 +49,13 @@ public: void BeginFrameVulkan(); // there's a BeginFrame in the base class, which this calls void EndFrame(); - void DeviceLost(); - void DeviceRestore(VulkanContext *vulkan, Draw::DrawContext *draw); + void DeviceLost() override; + void DeviceRestore(Draw::DrawContext *draw) override; + int GetLineWidth(); - void ReformatFramebufferFrom(VirtualFramebuffer *vfb, GEBufferFormat old) override; bool NotifyStencilUpload(u32 addr, int size, StencilUpload flags = StencilUpload::NEEDS_CLEAR) override; - VkImageView BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); - // If within a render pass, this will just issue a regular clear. If beginning a new render pass, // do that. void NotifyClear(bool clearColor, bool clearAlpha, bool clearDepth, uint32_t color, float depth); diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 197774e8d5..111ccffd48 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -231,7 +231,11 @@ void GPU_Vulkan::CheckGPUFeatures() { features |= GPU_SUPPORTS_INSTANCE_RENDERING; features |= GPU_SUPPORTS_VERTEX_TEXTURE_FETCH; features |= GPU_SUPPORTS_TEXTURE_FLOAT; - features |= GPU_PREFER_CPU_DOWNLOAD; + features |= GPU_SUPPORTS_DEPTH_TEXTURE; + + if (vulkan_->GetDeviceInfo().canBlitToPreferredDepthStencilFormat) { + features |= GPU_SUPPORTS_FRAMEBUFFER_BLIT_TO_DEPTH; + } if (vulkan_->GetDeviceFeatures().enabled.wideLines) { features |= GPU_SUPPORTS_WIDE_LINES; @@ -523,26 +527,25 @@ void GPU_Vulkan::DeviceLost() { SaveCache(shaderCachePath_); } DestroyDeviceObjects(); - framebufferManagerVulkan_->DeviceLost(); vulkan2D_.DeviceLost(); drawEngine_.DeviceLost(); pipelineManager_->DeviceLost(); textureCacheVulkan_->DeviceLost(); depalShaderCache_.DeviceLost(); shaderManagerVulkan_->ClearShaders(); - draw_ = nullptr; + + GPUCommon::DeviceLost(); } void GPU_Vulkan::DeviceRestore() { + GPUCommon::DeviceRestore(); vulkan_ = (VulkanContext *)PSP_CoreParameter().graphicsContext->GetAPIContext(); - draw_ = (Draw::DrawContext *)PSP_CoreParameter().graphicsContext->GetDrawContext(); InitDeviceObjects(); CheckGPUFeatures(); BuildReportingInfo(); UpdateCmdInfo(); - framebufferManagerVulkan_->DeviceRestore(vulkan_, draw_); vulkan2D_.DeviceRestore(vulkan_); drawEngine_.DeviceRestore(vulkan_, draw_); pipelineManager_->DeviceRestore(vulkan_); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 2aa0546a8e..8533e10b7d 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -325,6 +325,9 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip vulkanPipeline->flags |= PIPELINE_FLAG_USES_BLEND_CONSTANT; if (key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || key.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP) vulkanPipeline->flags |= PIPELINE_FLAG_USES_LINES; + if (dss.depthTestEnable || dss.stencilTestEnable) { + vulkanPipeline->flags |= PIPELINE_FLAG_USES_DEPTH_STENCIL; + } return vulkanPipeline; } @@ -550,7 +553,7 @@ void PipelineManagerVulkan::SetLineWidth(float lineWidth) { // Wipe all line-drawing pipelines. pipelines_.Iterate([&](const VulkanPipelineKey &key, VulkanPipeline *value) { - if (value->UsesLines()) { + if (value->flags & PIPELINE_FLAG_USES_LINES) { if (value->pipeline) vulkan_->Delete().QueueDeletePipeline(value->pipeline); delete value; @@ -619,7 +622,6 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha // Make sure the set of pipelines we write is "unique". std::set keys; - // TODO: Use derivative pipelines when possible, helps Mali driver pipeline creation speed at least. pipelines_.Iterate([&](const VulkanPipelineKey &pkey, VulkanPipeline *value) { if (failed) return; diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index d7d47fd0ef..8646d5b8bf 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -22,21 +22,11 @@ #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Common/ShaderId.h" +#include "GPU/Common/ShaderCommon.h" #include "GPU/Vulkan/VulkanUtil.h" #include "GPU/Vulkan/StateMappingVulkan.h" -// PSP vertex format. -enum class PspAttributeLocation { - POSITION = 0, - TEXCOORD = 1, - NORMAL = 2, - W1 = 3, - W2 = 4, - COLOR0 = 5, - COLOR1 = 6, - - COUNT -}; +#include "GPU/Vulkan/VulkanQueueRunner.h" struct VulkanPipelineKey { VulkanPipelineRasterStateKey raster; // prim is included here @@ -56,19 +46,14 @@ struct VulkanPipelineKey { std::string GetDescription(DebugShaderStringType stringType) const; }; -enum PipelineFlags { - PIPELINE_FLAG_USES_LINES = (1 << 2), - PIPELINE_FLAG_USES_BLEND_CONSTANT = (1 << 3), -}; - // Simply wraps a Vulkan pipeline, providing some metadata. struct VulkanPipeline { VkPipeline pipeline; int flags; // PipelineFlags enum above. - // Convenience. bool UsesBlendConstant() const { return (flags & PIPELINE_FLAG_USES_BLEND_CONSTANT) != 0; } bool UsesLines() const { return (flags & PIPELINE_FLAG_USES_LINES) != 0; } + bool UsesDepthStencil() const { return (flags & PIPELINE_FLAG_USES_DEPTH_STENCIL) != 0; } }; class VulkanContext; diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 1dff1d8ce0..4b54a77d83 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -36,11 +36,11 @@ #include "GPU/Math3D.h" #include "GPU/GPUState.h" #include "GPU/ge_constants.h" +#include "GPU/Common/FragmentShaderGenerator.h" +#include "GPU/Common/VertexShaderGenerator.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/DrawEngineVulkan.h" #include "GPU/Vulkan/FramebufferManagerVulkan.h" -#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" -#include "GPU/Vulkan/VertexShaderGeneratorVulkan.h" VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code) : vulkan_(vulkan), id_(id), failed_(false), module_(0) { @@ -53,7 +53,7 @@ VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, OutputDebugStringA(LineNumberString(code).c_str()); #endif - bool success = GLSLtoSPV(VK_SHADER_STAGE_FRAGMENT_BIT, code, spirv, &errorMessage); + bool success = GLSLtoSPV(VK_SHADER_STAGE_FRAGMENT_BIT, code, GLSLVariant::VULKAN, spirv, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); @@ -109,7 +109,7 @@ VulkanVertexShader::VulkanVertexShader(VulkanContext *vulkan, VShaderID id, cons #ifdef SHADERLOG OutputDebugStringA(LineNumberString(code).c_str()); #endif - bool success = GLSLtoSPV(VK_SHADER_STAGE_VERTEX_BIT, code, spirv, &errorMessage); + bool success = GLSLtoSPV(VK_SHADER_STAGE_VERTEX_BIT, code, GLSLVariant::VULKAN, spirv, &errorMessage); if (!errorMessage.empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); @@ -158,7 +158,7 @@ std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) cons } ShaderManagerVulkan::ShaderManagerVulkan(Draw::DrawContext *draw, VulkanContext *vulkan) - : ShaderManagerCommon(draw), vulkan_(vulkan), lastVShader_(nullptr), lastFShader_(nullptr), fsCache_(16), vsCache_(16) { + : ShaderManagerCommon(draw), vulkan_(vulkan), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16) { codeBuffer_ = new char[16384]; uboAlignment_ = vulkan_->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment; memset(&ub_base, 0, sizeof(ub_base)); @@ -262,7 +262,11 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader VulkanVertexShader *vs = vsCache_.Get(VSID); if (!vs) { // Vertex shader not in cache. Let's compile it. - GenerateVulkanGLSLVertexShader(VSID, codeBuffer_); + std::string genErrorString; + uint64_t uniformMask = 0; // Not used + uint32_t attributeMask = 0; // Not used + bool success = GenerateVertexShader(VSID, codeBuffer_, compat_, &attributeMask, &uniformMask, &genErrorString); + _assert_(success); vs = new VulkanVertexShader(vulkan_, VSID, codeBuffer_, useHWTransform); vsCache_.Insert(VSID, vs); } @@ -270,9 +274,12 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader VulkanFragmentShader *fs = fsCache_.Get(FSID); if (!fs) { - uint32_t vendorID = vulkan_->GetPhysicalDeviceProperties().properties.vendorID; + // uint32_t vendorID = vulkan_->GetPhysicalDeviceProperties().properties.vendorID; // Fragment shader not in cache. Let's compile it. - GenerateVulkanGLSLFragmentShader(FSID, codeBuffer_, vendorID); + std::string genErrorString; + uint64_t uniformMask = 0; // Not used + bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, &uniformMask, &genErrorString); + _assert_(success); fs = new VulkanFragmentShader(vulkan_, FSID, codeBuffer_); fsCache_.Insert(FSID, fs); } @@ -361,7 +368,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 18 +#define CACHE_VERSION 19 struct VulkanCacheHeader { uint32_t magic; uint32_t version; @@ -388,18 +395,28 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) { break; } bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); - GenerateVulkanGLSLVertexShader(id, codeBuffer_); + std::string genErrorString; + uint32_t attributeMask = 0; + uint64_t uniformMask = 0; + if (!GenerateVertexShader(id, codeBuffer_, compat_, &attributeMask, &uniformMask, &genErrorString)) { + return false; + } VulkanVertexShader *vs = new VulkanVertexShader(vulkan_, id, codeBuffer_, useHWTransform); vsCache_.Insert(id, vs); } uint32_t vendorID = vulkan_->GetPhysicalDeviceProperties().properties.vendorID; + for (int i = 0; i < header.numFragmentShaders; i++) { FShaderID id; if (fread(&id, sizeof(id), 1, f) != 1) { ERROR_LOG(G3D, "Vulkan shader cache truncated"); break; } - GenerateVulkanGLSLFragmentShader(id, codeBuffer_, vendorID); + std::string genErrorString; + uint64_t uniformMask = 0; + if (!GenerateFragmentShader(id, codeBuffer_, compat_, &uniformMask, &genErrorString)) { + return false; + } VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan_, id, codeBuffer_); fsCache_.Insert(id, fs); } diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index 92a52594ff..e35a07a103 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -24,8 +24,8 @@ #include "Common/GPU/Vulkan/VulkanMemory.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/ShaderId.h" -#include "GPU/Vulkan/VertexShaderGeneratorVulkan.h" -#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" +#include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Vulkan/VulkanUtil.h" #include "Common/Math/lin/matrix4x4.h" #include "GPU/Common/ShaderUniforms.h" @@ -131,6 +131,7 @@ private: void Clear(); VulkanContext *vulkan_; + ShaderLanguageDesc compat_; typedef DenseHashMap FSCache; FSCache fsCache_; @@ -146,8 +147,8 @@ private: UB_VS_Lights ub_lights; UB_VS_Bones ub_bones; - VulkanFragmentShader *lastFShader_; - VulkanVertexShader *lastVShader_; + VulkanFragmentShader *lastFShader_ = nullptr; + VulkanVertexShader *lastVShader_ = nullptr; FShaderID lastFSID_; VShaderID lastVSID_; diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index 1dee71861d..a636531e53 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -122,7 +122,7 @@ static const VkLogicOp logicOps[] = { VK_LOGIC_OP_SET, }; -void DrawEngineVulkan::ResetShaderBlending() { +void DrawEngineVulkan::ResetFramebufferRead() { boundSecondary_ = VK_NULL_HANDLE; } @@ -135,7 +135,7 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag bool useBufferedRendering = framebufferManager_->UseBufferedRendering(); if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) { - gstate_c.SetAllowShaderBlend(!g_Config.bDisableSlowFramebufEffects); + gstate_c.SetAllowFramebufferRead(!g_Config.bDisableSlowFramebufEffects); if (gstate.isModeClear()) { key.logicOpEnable = false; key.logicOp = VK_LOGIC_OP_CLEAR; @@ -163,21 +163,25 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag // Set blend - unless we need to do it in the shader. GenericBlendState blendState; - ConvertBlendState(blendState, gstate_c.allowShaderBlend); + ConvertBlendState(blendState, gstate_c.allowFramebufferRead); - if (blendState.applyShaderBlending) { - if (ApplyShaderBlending()) { - // We may still want to do something about stencil -> alpha. - ApplyStencilReplaceAndLogicOp(blendState.replaceAlphaWithStencil, blendState); + GenericMaskState maskState; + ConvertMaskState(maskState, gstate_c.allowFramebufferRead); + + if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { + if (ApplyFramebufferRead(&fboTexNeedsBind_)) { + // The shader takes over the responsibility for blending, so recompute. + ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState); } else { // Until next time, force it off. - ResetShaderBlending(); - gstate_c.SetAllowShaderBlend(false); + ResetFramebufferRead(); + gstate_c.SetAllowFramebufferRead(false); // Make sure we recompute the fragment shader ID to one that doesn't try to use shader blending. - gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } - } else if (blendState.resetShaderBlending) { - ResetShaderBlending(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); + } else if (blendState.resetFramebufferRead) { + ResetFramebufferRead(); + gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } if (blendState.enabled) { @@ -188,7 +192,7 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag key.srcAlpha = vkBlendFactorLookup[(size_t)blendState.srcAlpha]; key.destColor = vkBlendFactorLookup[(size_t)blendState.dstColor]; key.destAlpha = vkBlendFactorLookup[(size_t)blendState.dstAlpha]; - if (blendState.dirtyShaderBlend) { + if (blendState.dirtyShaderBlendFixValues) { gstate_c.Dirty(DIRTY_SHADERBLEND); } dynState.useBlendColor = blendState.useBlendColor; @@ -206,38 +210,11 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag dynState.useBlendColor = false; } - // PSP color/alpha mask is per bit but we can only support per byte. - // But let's do that, at least. And let's try a threshold. - bool rmask = (gstate.pmskc & 0xFF) < 128; - bool gmask = ((gstate.pmskc >> 8) & 0xFF) < 128; - bool bmask = ((gstate.pmskc >> 16) & 0xFF) < 128; - bool amask = (gstate.pmska & 0xFF) < 128; - -#ifndef MOBILE_DEVICE - u8 abits = (gstate.pmska >> 0) & 0xFF; - u8 rbits = (gstate.pmskc >> 0) & 0xFF; - u8 gbits = (gstate.pmskc >> 8) & 0xFF; - u8 bbits = (gstate.pmskc >> 16) & 0xFF; - if ((rbits != 0 && rbits != 0xFF) || (gbits != 0 && gbits != 0xFF) || (bbits != 0 && bbits != 0xFF)) { - WARN_LOG_REPORT_ONCE(rgbmask, G3D, "Unsupported RGB mask: r=%02x g=%02x b=%02x", rbits, gbits, bbits); - } - if (abits != 0 && abits != 0xFF) { - // The stencil part of the mask is supported. - WARN_LOG_REPORT_ONCE(amask, G3D, "Unsupported alpha/stencil mask: %02x", abits); - } -#endif - - // Let's not write to alpha if stencil isn't enabled. - if (IsStencilTestOutputDisabled()) { - amask = false; - } else { - // If the stencil type is set to KEEP, we shouldn't write to the stencil/alpha channel. - if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_KEEP) { - amask = false; - } - } - - key.colorWriteMask = (rmask ? VK_COLOR_COMPONENT_R_BIT : 0) | (gmask ? VK_COLOR_COMPONENT_G_BIT : 0) | (bmask ? VK_COLOR_COMPONENT_B_BIT : 0) | (amask ? VK_COLOR_COMPONENT_A_BIT : 0); + key.colorWriteMask = + (maskState.rgba[0] ? VK_COLOR_COMPONENT_R_BIT : 0) | + (maskState.rgba[1] ? VK_COLOR_COMPONENT_G_BIT : 0) | + (maskState.rgba[2] ? VK_COLOR_COMPONENT_B_BIT : 0) | + (maskState.rgba[3] ? VK_COLOR_COMPONENT_A_BIT : 0); // Workaround proposed in #10421, for bug where the color write mask is not applied correctly on Adreno. if ((gstate.pmskc & 0x00FFFFFF) == 0x00FFFFFF && g_Config.bVendorBugChecksEnabled && draw_->GetBugs().Has(Draw::Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST)) { @@ -391,20 +368,16 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag } void DrawEngineVulkan::BindShaderBlendTex() { - // At this point, we know if the vertices are full alpha or not. - // TODO: Set the nearest/linear here (since we correctly know if alpha/color tests are needed)? + // TODO: At this point, we know if the vertices are full alpha or not. + // Set the nearest/linear here (since we correctly know if alpha/color tests are needed)? if (!gstate.isModeClear()) { - // TODO: Test texture? - if (fboTexNeedBind_) { - // Note that this is positions, not UVs, that we need the copy from. + if (fboTexNeedsBind_) { framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY); - // If we are rendering at a higher resolution, linear is probably best for the dest color. boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW); fboTexBound_ = true; - fboTexNeedBind_ = false; + fboTexNeedsBind_ = false; } } - } void DrawEngineVulkan::ApplyDrawStateLate(VulkanRenderManager *renderManager, bool applyStencilRef, uint8_t stencilRef, bool useBlendConstant) { diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index faaba54836..28d9132462 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -22,7 +22,6 @@ #include "Core/Reporting.h" #include "GPU/Common/StencilCommon.h" #include "GPU/Vulkan/FramebufferManagerVulkan.h" -#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/TextureCacheVulkan.h" #include "GPU/Vulkan/VulkanUtil.h" @@ -180,7 +179,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, StencilUp } VkPipeline pipeline = vulkan2D_->GetPipeline(rp, stencilVs_, stencilFs_, false, Vulkan2D::VK2DDepthStencilMode::STENCIL_REPLACE_ALWAYS); - renderManager->BindPipeline(pipeline); + renderManager->BindPipeline(pipeline, PIPELINE_FLAG_USES_DEPTH_STENCIL); renderManager->SetViewport({ 0.0f, 0.0f, (float)w, (float)h, 0.0f, 1.0f }); renderManager->SetScissor({ { 0, 0, },{ (uint32_t)w, (uint32_t)h } }); diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 54210f8a60..8fdcd6d8f5 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -45,7 +45,6 @@ #include "GPU/Common/TextureDecoder.h" #include "GPU/Vulkan/TextureCacheVulkan.h" #include "GPU/Vulkan/FramebufferManagerVulkan.h" -#include "GPU/Vulkan/FragmentShaderGeneratorVulkan.h" #include "GPU/Vulkan/DepalettizeShaderVulkan.h" #include "GPU/Vulkan/ShaderManagerVulkan.h" #include "GPU/Vulkan/DrawEngineVulkan.h" @@ -559,7 +558,11 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer TexCacheEntry::TexStatus alphaStatus = CheckAlpha(clutBuf_, getClutDestFormatVulkan(clutFormat), clutTotalColors, clutTotalColors, 1); gstate_c.SetTextureFullAlpha(alphaStatus == TexCacheEntry::STATUS_ALPHA_FULL); curSampler_ = samplerCache_.GetOrCreateSampler(samplerKey); - imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET); + if (framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET)) { + imageView_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE0_IMAGEVIEW); + } else { + imageView_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::NULL_IMAGEVIEW); + } return; } else { depalShader = depalShaderCache_->GetDepalettizeShader(clutMode, depth ? GE_FORMAT_DEPTH16 : framebuffer->drawnFormat); @@ -572,7 +575,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); VulkanTexture *clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_, expand32); - Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); + Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, framebuffer->renderWidth, framebuffer->renderHeight); draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); Vulkan2D::Vertex verts[4] = { @@ -635,7 +638,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer VkDescriptorSet descSet = vulkan2D_->GetDescriptorSet(fbo, samplerNearest_, clutTexture->GetImageView(), samplerNearest_); VulkanRenderManager *renderManager = (VulkanRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - renderManager->BindPipeline(depalShader->pipeline); + renderManager->BindPipeline(depalShader->pipeline, (PipelineFlags)0); if (depth) { DepthScaleFactors scaleFactors = GetDepthScaleFactors(); @@ -668,7 +671,12 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer // 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 { - imageView_ = framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET); + if (framebufferManagerVulkan_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_MAY_COPY_WITH_UV | BINDFBCOLOR_APPLY_TEX_OFFSET)) { + imageView_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE0_IMAGEVIEW); + } else { + imageView_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::NULL_IMAGEVIEW); + } + drawEngine_->SetDepalTexture(VK_NULL_HANDLE); gstate_c.SetUseShaderDepal(false); diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index a61ba47d20..254efcd40b 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -110,7 +110,7 @@ protected: private: void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); VkFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; - TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h); + static TexCacheEntry::TexStatus CheckAlpha(const u32 *pixelData, VkFormat dstFmt, int stride, int w, int h); void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override; void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, FramebufferNotificationChannel channel) override; diff --git a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp deleted file mode 100644 index 36574f24ae..0000000000 --- a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp +++ /dev/null @@ -1,645 +0,0 @@ -// Copyright (c) 2012- 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/. - -#include -#include -#include - -#include "Common/GPU/OpenGL/GLFeatures.h" - -#if defined(_WIN32) && defined(_DEBUG) -#include "Common/CommonWindows.h" -#endif - -#include "Common/StringUtils.h" -#include "Common/GPU/Vulkan/VulkanLoader.h" -#include "Core/Config.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUState.h" -#include "GPU/Common/ShaderId.h" -#include "GPU/Common/VertexDecoderCommon.h" -#include "GPU/Vulkan/VertexShaderGeneratorVulkan.h" -#include "GPU/Vulkan/PipelineManagerVulkan.h" -#include "GPU/Vulkan/ShaderManagerVulkan.h" - -static const char *vulkan_glsl_preamble = -"#version 450\n" -"#extension GL_ARB_separate_shader_objects : enable\n" -"#extension GL_ARB_shading_language_420pack : enable\n\n"; - -// "Varying" layout - must match fragment shader -// color0 = 0 -// color1 = 1 -// texcoord = 2 -// fog = 3 - -#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, - LIGHT_FULL, -}; - -// Depth range and viewport -// -// After the multiplication with the projection matrix, we have a 4D vector in clip space. -// In OpenGL, Z is from -1 to 1, while in D3D, Z is from 0 to 1. -// PSP appears to use the OpenGL convention. As Z is from -1 to 1, and the viewport is represented -// by a center and a scale, to find the final Z value, all we need to do is to multiply by ZScale and -// add ZCenter - these are properly scaled to directly give a Z value in [0, 65535]. -// -// z = vec.z * ViewportZScale + ViewportZCenter; -// -// That will give us the final value between 0 and 65535, which we can simply floor to simulate -// the limited precision of the PSP's depth buffer. Then we convert it back: -// z = floor(z); -// -// vec.z = (z - ViewportZCenter) / ViewportZScale; -// -// Now, the regular machinery will take over and do the calculation again. -// -// All this above is for full transform mode. -// In through mode, the Z coordinate just goes straight through and there is no perspective division. -// We simulate this of course with pretty much an identity matrix. Rounding Z becomes very easy. -// -// TODO: Skip all this if we can actually get a 16-bit depth buffer along with stencil, which -// is a bit of a rare configuration, although quite common on mobile. - -bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) { - char *p = buffer; - - WRITE(p, "%s", vulkan_glsl_preamble); - - bool highpFog = false; - bool highpTexcoord = false; - - bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH); - bool lmode = id.Bit(VS_BIT_LMODE); - bool doTexture = id.Bit(VS_BIT_DO_TEXTURE); - bool doTextureTransform = id.Bit(VS_BIT_DO_TEXTURE_TRANSFORM); - - GETexMapMode uvGenMode = static_cast(id.Bits(VS_BIT_UVGEN_MODE, 2)); - - // this is only valid for some settings of uvGenMode - GETexProjMapMode uvProjMode = static_cast(id.Bits(VS_BIT_UVPROJ_MODE, 2)); - bool doShadeMapping = uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP; - bool doFlatShading = id.Bit(VS_BIT_FLATSHADE); - - bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); - bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; - bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform; - bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform; - bool enableFog = id.Bit(VS_BIT_ENABLE_FOG); - 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); - - bool doBezier = id.Bit(VS_BIT_BEZIER); - bool doSpline = id.Bit(VS_BIT_SPLINE); - bool hasColorTess = id.Bit(VS_BIT_HAS_COLOR_TESS); - bool hasTexcoordTess = id.Bit(VS_BIT_HAS_TEXCOORD_TESS); - bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS); - bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS); - - WRITE(p, "\n"); - 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 = 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 " : ""; - - DoLightComputation doLight[4] = { LIGHT_OFF, LIGHT_OFF, LIGHT_OFF, LIGHT_OFF }; - if (useHWTransform) { - int shadeLight0 = doShadeMapping ? ls0 : -1; - int shadeLight1 = doShadeMapping ? ls1 : -1; - for (int i = 0; i < 4; i++) { - if (i == shadeLight0 || i == shadeLight1) - doLight[i] = LIGHT_SHADE; - if (id.Bit(VS_BIT_LIGHTING_ENABLE) && id.Bit(VS_BIT_LIGHT0_ENABLE + i)) - doLight[i] = LIGHT_FULL; - } - } - - 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 - // we pass the fog coord in w - WRITE(p, "layout (location = %d) in vec4 position;\n", (int)PspAttributeLocation::POSITION); - - if (useHWTransform && hasNormal) - WRITE(p, "layout (location = %d) in vec3 normal;\n", (int)PspAttributeLocation::NORMAL); - - bool texcoordInVec3 = false; - if (doTexture && hasTexcoord) { - if (!useHWTransform && doTextureTransform && !isModeThrough) { - WRITE(p, "layout (location = %d) in vec3 texcoord;\n", (int)PspAttributeLocation::TEXCOORD); - texcoordInVec3 = true; - } else - WRITE(p, "layout (location = %d) in vec2 texcoord;\n", (int)PspAttributeLocation::TEXCOORD); - } - if (hasColor) { - WRITE(p, "layout (location = %d) in vec4 color0;\n", (int)PspAttributeLocation::COLOR0); - if (lmode && !useHWTransform) // only software transform supplies color1 as vertex data - WRITE(p, "layout (location = %d) in vec3 color1;\n", (int)PspAttributeLocation::COLOR1); - } - - WRITE(p, "layout (location = 1) %sout vec4 v_color0;\n", shading); - if (lmode) { - WRITE(p, "layout (location = 2) %sout vec3 v_color1;\n", shading); - } - - if (doTexture) { - WRITE(p, "layout (location = 0) out vec3 v_texcoord;\n"); - } - - if (enableFog) { - // See the fragment shader generator - WRITE(p, "layout (location = 3) out float v_fogdepth;\n"); - } - - // See comment above this function (GenerateVertexShader). - if (!isModeThrough && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. - WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n"); - WRITE(p, " float z = v.z / v.w;\n"); - WRITE(p, " z = z * base.depthRange.x + base.depthRange.y;\n"); - WRITE(p, " z = floor(z);\n"); - WRITE(p, " z = (z - base.depthRange.z) * base.depthRange.w;\n"); - WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n"); - WRITE(p, "}\n\n"); - } - - if (doBezier || doSpline) { - WRITE(p, "struct TessData {\n"); - WRITE(p, " vec4 pos;\n"); - WRITE(p, " vec4 uv;\n"); - WRITE(p, " vec4 color;\n"); - WRITE(p, "};\n"); - WRITE(p, "layout (std430, set = 0, binding = 6) readonly buffer s_tess_data {\n"); - WRITE(p, " TessData data[];\n"); - WRITE(p, "} tess_data;\n"); - - WRITE(p, "layout (std430) struct TessWeight {\n"); - WRITE(p, " vec4 basis;\n"); - WRITE(p, " vec4 deriv;\n"); - WRITE(p, "};\n"); - WRITE(p, "layout (std430, set = 0, binding = 7) readonly buffer s_tess_weights_u {\n"); - WRITE(p, " TessWeight data[];\n"); - WRITE(p, "} tess_weights_u;\n"); - WRITE(p, "layout (std430, set = 0, binding = 8) readonly buffer s_tess_weights_v {\n"); - WRITE(p, " TessWeight data[];\n"); - WRITE(p, "} tess_weights_v;\n"); - - for (int i = 2; i <= 4; i++) { - // Define 3 types vec2, vec3, vec4 - WRITE(p, "vec%d tess_sample(in vec%d points[16], mat4 weights) {\n", i, i); - WRITE(p, " vec%d pos = vec%d(0.0);\n", i, i); - for (int v = 0; v < 4; ++v) { - for (int u = 0; u < 4; ++u) { - WRITE(p, " pos += weights[%i][%i] * points[%i];\n", v, u, v * 4 + u); - } - } - WRITE(p, " return pos;\n"); - WRITE(p, "}\n"); - } - - WRITE(p, "struct Tess {\n"); - WRITE(p, " vec3 pos;\n"); - if (doTexture) - WRITE(p, " vec2 tex;\n"); - WRITE(p, " vec4 col;\n"); - if (hasNormalTess) - WRITE(p, " vec3 nrm;\n"); - WRITE(p, "};\n"); - - WRITE(p, "void tessellate(out Tess tess) {\n"); - WRITE(p, " ivec2 point_pos = ivec2(position.z, normal.z)%s;\n", doBezier ? " * 3" : ""); - WRITE(p, " ivec2 weight_idx = ivec2(position.xy);\n"); - // Load 4x4 control points - WRITE(p, " vec3 _pos[16];\n"); - WRITE(p, " vec2 _tex[16];\n"); - WRITE(p, " vec4 _col[16];\n"); - WRITE(p, " int index;\n"); - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - WRITE(p, " index = (%i + point_pos.y) * int(base.spline_counts) + (%i + point_pos.x);\n", i, j); - WRITE(p, " _pos[%i] = tess_data.data[index].pos.xyz;\n", i * 4 + j); - if (doTexture && hasTexcoordTess) - WRITE(p, " _tex[%i] = tess_data.data[index].uv.xy;\n", i * 4 + j); - if (hasColorTess) - WRITE(p, " _col[%i] = tess_data.data[index].color;\n", i * 4 + j); - } - } - - // Basis polynomials as weight coefficients - WRITE(p, " vec4 basis_u = tess_weights_u.data[weight_idx.x].basis;\n"); - WRITE(p, " vec4 basis_v = tess_weights_v.data[weight_idx.y].basis;\n"); - WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n"); - - // Tessellate - WRITE(p, " tess.pos = tess_sample(_pos, basis);\n"); - if (doTexture) { - if (hasTexcoordTess) - WRITE(p, " tess.tex = tess_sample(_tex, basis);\n"); - else - WRITE(p, " tess.tex = normal.xy;\n"); - } - if (hasColorTess) - WRITE(p, " tess.col = tess_sample(_col, basis);\n"); - else - WRITE(p, " tess.col = base.matambientalpha;\n"); - if (hasNormalTess) { - // Derivatives as weight coefficients - WRITE(p, " vec4 deriv_u = tess_weights_u.data[weight_idx.x].deriv;\n"); - WRITE(p, " vec4 deriv_v = tess_weights_v.data[weight_idx.y].deriv;\n"); - - WRITE(p, " vec3 du = tess_sample(_pos, outerProduct(deriv_u, basis_v));\n"); - WRITE(p, " vec3 dv = tess_sample(_pos, outerProduct(basis_u, deriv_v));\n"); - WRITE(p, " tess.nrm = normalize(cross(du, dv));\n"); - } - WRITE(p, "}\n"); - } - - WRITE(p, "void main() {\n"); - - if (!useHWTransform) { - // Simple pass-through of vertex data to fragment shader - if (doTexture) { - if (texcoordInVec3) { - WRITE(p, " v_texcoord = texcoord;\n"); - } else { - WRITE(p, " v_texcoord = vec3(texcoord, 1.0);\n"); - } - } - if (hasColor) { - WRITE(p, " v_color0 = color0;\n"); - if (lmode) - WRITE(p, " v_color1 = color1;\n"); - } else { - WRITE(p, " v_color0 = base.matambientalpha;\n"); - if (lmode) - WRITE(p, " v_color1 = vec3(0.0);\n"); - } - if (enableFog) { - WRITE(p, " v_fogdepth = position.w;\n"); - } - if (isModeThrough) { - WRITE(p, " vec4 outPos = base.proj_through_mtx * vec4(position.xyz, 1.0);\n"); - } else { - // The viewport is used in this case, so need to compensate for that. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " vec4 outPos = depthRoundZVP(base.proj_mtx * vec4(position.xyz, 1.0));\n"); - } else { - WRITE(p, " vec4 outPos = base.proj_mtx * vec4(position.xyz, 1.0);\n"); - } - } - } else { - // Step 1: World Transform / Skinning - if (!enableBones) { - if (doBezier || doSpline) { - // Hardware tessellation - WRITE(p, " Tess tess;\n"); - WRITE(p, " tessellate(tess);\n"); - - WRITE(p, " vec3 worldpos = vec4(tess.pos.xyz, 1.0) * base.world_mtx;\n"); - if (hasNormalTess) { - WRITE(p, " mediump vec3 worldnormal = normalize(vec4(%stess.nrm, 0.0) * base.world_mtx);\n", flipNormalTess ? "-" : ""); - } else { - WRITE(p, " mediump vec3 worldnormal = vec3(0.0, 0.0, 1.0);\n"); - } - } else { - // 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 { - 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"); - - // Final view and projection transforms. - if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) { - WRITE(p, " vec4 outPos = depthRoundZVP(base.proj_mtx * viewPos);\n"); - } else { - WRITE(p, " vec4 outPos = base.proj_mtx * viewPos;\n"); - } - - // TODO: Declare variables for dots for shade mapping if needed. - - const char *ambientStr = ((matUpdate & 1) && hasColor) ? "color0" : "base.matambientalpha"; - const char *diffuseStr = ((matUpdate & 2) && hasColor) ? "color0.rgb" : "light.matdiffuse"; - const char *specularStr = ((matUpdate & 4) && hasColor) ? "color0.rgb" : "light.matspecular.rgb"; - if (doBezier || doSpline) { - // TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background. - ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "base.matambientalpha"; - diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "light.matdiffuse"; - specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "light.matspecular.rgb"; - } - - bool diffuseIsZero = true; - bool specularIsZero = true; - bool distanceNeeded = false; - - if (enableLighting) { - WRITE(p, " vec4 lightSum0 = light.u_ambient * %s + vec4(light.matemissive, 0.0);\n", ambientStr); - - for (int i = 0; i < 4; i++) { - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - if (doLight[i] != LIGHT_FULL) - continue; - diffuseIsZero = false; - if (comp == GE_LIGHTCOMP_BOTH) - specularIsZero = false; - if (type != GE_LIGHTTYPE_DIRECTIONAL) - distanceNeeded = true; - } - - if (!specularIsZero) { - WRITE(p, " vec3 lightSum1 = vec3(0.0);\n"); - } - if (!diffuseIsZero) { - WRITE(p, " vec3 toLight;\n"); - WRITE(p, " vec3 diffuse;\n"); - } - if (distanceNeeded) { - WRITE(p, " float distance;\n"); - WRITE(p, " float lightScale;\n"); - } - } - - // Calculate lights if needed. If shade mapping is enabled, lights may need to be - // at least partially calculated. - for (int i = 0; i < 4; i++) { - if (doLight[i] != LIGHT_FULL) - continue; - - GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2)); - GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2)); - - if (type == GE_LIGHTTYPE_DIRECTIONAL) { - // We prenormalize light positions for directional lights. - WRITE(p, " toLight = light.pos[%i];\n", i); - } else { - WRITE(p, " toLight = light.pos[%i] - worldpos;\n", i); - WRITE(p, " distance = length(toLight);\n"); - WRITE(p, " toLight /= distance;\n"); - } - - bool doSpecular = comp == GE_LIGHTCOMP_BOTH; - bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE; - - WRITE(p, " mediump float dot%i = dot(toLight, worldnormal);\n", i); - if (poweredDiffuse) { - // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0. - // Seen in Tales of the World: Radiant Mythology (#2424.) - WRITE(p, " if (light.matspecular.a <= 0.0) {\n"); - WRITE(p, " dot%i = 1.0;\n", i); - WRITE(p, " } else {\n"); - WRITE(p, " dot%i = pow(max(dot%i, 0.0), light.matspecular.a);\n", i, i); - WRITE(p, " }\n"); - } - - const char *timesLightScale = " * lightScale"; - - // Attenuation - switch (type) { - case GE_LIGHTTYPE_DIRECTIONAL: - timesLightScale = ""; - break; - case GE_LIGHTTYPE_POINT: - WRITE(p, " lightScale = clamp(1.0 / dot(light.att[%i], vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i); - break; - case GE_LIGHTTYPE_SPOT: - case GE_LIGHTTYPE_UNKNOWN: - WRITE(p, " float angle%i = length(light.dir[%i]) == 0.0 ? 0.0 : dot(normalize(light.dir[%i]), toLight);\n", i, i, i); - WRITE(p, " if (angle%i >= light.angle_spotCoef[%i].x) {\n", i, i); - WRITE(p, " lightScale = clamp(1.0 / dot(light.att[%i], vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (light.angle_spotCoef[%i].y <= 0.0 ? 1.0 : pow(angle%i, light.angle_spotCoef[%i].y));\n", i, i, i, i); - WRITE(p, " } else {\n"); - WRITE(p, " lightScale = 0.0;\n"); - WRITE(p, " }\n"); - break; - default: - // ILLEGAL - break; - } - - WRITE(p, " diffuse = (light.diffuse[%i] * %s) * max(dot%i, 0.0);\n", i, diffuseStr, i); - if (doSpecular) { - WRITE(p, " if (dot%i >= 0.0) {\n", i); - WRITE(p, " dot%i = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n", i); - WRITE(p, " if (light.matspecular.a <= 0.0) {\n"); - WRITE(p, " dot%i = 1.0;\n", i); - WRITE(p, " } else {\n"); - WRITE(p, " dot%i = pow(max(dot%i, 0.0), light.matspecular.a);\n", i, i); - WRITE(p, " }\n"); - WRITE(p, " if (dot%i > 0.0)\n", i); - WRITE(p, " lightSum1 += light.specular[%i] * %s * dot%i %s;\n", i, specularStr, i, timesLightScale); - WRITE(p, " }\n"); - } - WRITE(p, " lightSum0.rgb += (light.ambient[%i] * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale); - } - - if (enableLighting) { - // Sum up ambient, emissive here. - if (lmode) { - WRITE(p, " v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - // v_color1 only exists when lmode = 1. - if (specularIsZero) { - WRITE(p, " v_color1 = vec3(0.0);\n"); - } else { - WRITE(p, " v_color1 = clamp(lightSum1, 0.0, 1.0);\n"); - } - } else { - if (specularIsZero) { - WRITE(p, " v_color0 = clamp(lightSum0, 0.0, 1.0);\n"); - } else { - WRITE(p, " v_color0 = clamp(clamp(lightSum0, 0.0, 1.0) + vec4(lightSum1, 0.0), 0.0, 1.0);\n"); - } - } - } else { - // Lighting doesn't affect color. - if (hasColor) { - if (doBezier || doSpline) - WRITE(p, " v_color0 = tess.col;\n"); - else - WRITE(p, " v_color0 = color0;\n"); - } else { - WRITE(p, " v_color0 = base.matambientalpha;\n"); - } - if (lmode) { - WRITE(p, " v_color1 = vec3(0.0);\n"); - } - } - - bool scaleUV = !isModeThrough && (uvGenMode == GE_TEXMAP_TEXTURE_COORDS || uvGenMode == GE_TEXMAP_UNKNOWN); - - // Step 3: UV generation - if (doTexture) { - switch (uvGenMode) { - case GE_TEXMAP_TEXTURE_COORDS: // Scale-offset. Easy. - case GE_TEXMAP_UNKNOWN: // Not sure what this is, but Riviera uses it. Treating as coords works. - if (scaleUV) { - if (hasTexcoord) { - if (doBezier || doSpline) - WRITE(p, " v_texcoord = vec3(tess.tex.xy * base.uvscaleoffset.xy + base.uvscaleoffset.zw, 0.0);\n"); - else - WRITE(p, " v_texcoord = vec3(texcoord.xy * base.uvscaleoffset.xy, 0.0);\n"); - } else { - WRITE(p, " v_texcoord = vec3(0.0);\n"); - } - } else { - if (hasTexcoord) { - WRITE(p, " v_texcoord = vec3(texcoord.xy * base.uvscaleoffset.xy + base.uvscaleoffset.zw, 0.0);\n"); - } else { - WRITE(p, " v_texcoord = vec3(base.uvscaleoffset.zw, 0.0);\n"); - } - } - break; - - case GE_TEXMAP_TEXTURE_MATRIX: // Projection mapping. - { - std::string temp_tc; - switch (uvProjMode) { - case GE_PROJMAP_POSITION: // Use model space XYZ as source - temp_tc = "vec4(position.xyz, 1.0)"; - break; - case GE_PROJMAP_UV: // Use unscaled UV as source - { - // scaleUV is false here. - if (hasTexcoord) { - temp_tc = "vec4(texcoord.xy, 0.0, 1.0)"; - } else { - temp_tc = "vec4(0.0, 0.0, 0.0, 1.0)"; - } - } - break; - case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "vec4(normalize(-normal), 1.0)" : "vec4(normalize(normal), 1.0)"; - else - temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; - break; - case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source - if (hasNormal) - temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)"; - else - temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)"; - break; - } - // Transform by texture matrix. XYZ as we are doing projection mapping. - WRITE(p, " v_texcoord = (%s * base.tex_mtx).xyz * vec3(base.uvscaleoffset.xy, 1.0);\n", temp_tc.c_str()); - } - break; - - case GE_TEXMAP_ENVIRONMENT_MAP: // Shade mapping - use dots from light sources. - { - std::string lightFactor0 = StringFromFormat("(length(light.pos[%i]) == 0.0 ? worldnormal.z : dot(normalize(light.pos[%i]), worldnormal))", ls0, ls0); - std::string lightFactor1 = StringFromFormat("(length(light.pos[%i]) == 0.0 ? worldnormal.z : dot(normalize(light.pos[%i]), worldnormal))", ls1, ls1); - WRITE(p, " v_texcoord = vec3(base.uvscaleoffset.xy * vec2(1.0 + %s, 1.0 + %s) * 0.5, 1.0);\n", lightFactor0.c_str(), lightFactor1.c_str()); - } - break; - - default: - // ILLEGAL - break; - } - } - - // Compute fogdepth - if (enableFog) - WRITE(p, " v_fogdepth = (viewPos.z + base.fogcoef.x) * base.fogcoef.y;\n"); - } - - if (!isModeThrough && gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING)) { - WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n"); - // Vertex range culling doesn't happen when depth is clamped, so only do this if in range. - WRITE(p, " if (base.cullRangeMin.w <= 0.0 || (projPos.z >= base.cullRangeMin.z && projPos.z <= base.cullRangeMax.z)) {\n"); - const char *outMin = "projPos.x < base.cullRangeMin.x || projPos.y < base.cullRangeMin.y || projPos.z < base.cullRangeMin.z"; - const char *outMax = "projPos.x > base.cullRangeMax.x || projPos.y > base.cullRangeMax.y || projPos.z > base.cullRangeMax.z"; - WRITE(p, " if (%s || %s) {\n", outMin, outMax); - WRITE(p, " outPos.xyzw = vec4(base.cullRangeMax.w);\n"); - WRITE(p, " }\n"); - WRITE(p, " }\n"); - } - WRITE(p, " gl_Position = outPos;\n"); - WRITE(p, " gl_PointSize = 1.0;\n"); - - if (gstate_c.Supports(GPU_NEEDS_Z_EQUAL_W_HACK)) { - // See comment in GPU_Vulkan.cpp. - WRITE(p, " if (gl_Position.z == gl_Position.w) gl_Position.z *= 0.999999;\n"); - } - WRITE(p, "}\n"); - return true; -} diff --git a/GPU/Vulkan/VertexShaderGeneratorVulkan.h b/GPU/Vulkan/VertexShaderGeneratorVulkan.h deleted file mode 100644 index 1501c1cdae..0000000000 --- a/GPU/Vulkan/VertexShaderGeneratorVulkan.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "GPU/Common/ShaderId.h" - -bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer); diff --git a/GPU/Vulkan/VulkanUtil.cpp b/GPU/Vulkan/VulkanUtil.cpp index bcf6f655f9..fa2deab322 100644 --- a/GPU/Vulkan/VulkanUtil.cpp +++ b/GPU/Vulkan/VulkanUtil.cpp @@ -196,11 +196,7 @@ VkDescriptorSet Vulkan2D::GetDescriptorSet(VkImageView tex1, VkSampler sampler1, VkDescriptorImageInfo image1{}; VkDescriptorImageInfo image2{}; if (tex1 && sampler1) { -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - image1.imageLayout = VK_IMAGE_LAYOUT_GENERAL; -#else image1.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -#endif image1.imageView = tex1; image1.sampler = sampler1; writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -213,11 +209,7 @@ VkDescriptorSet Vulkan2D::GetDescriptorSet(VkImageView tex1, VkSampler sampler1, } if (tex2 && sampler2) { // TODO: Also support LAYOUT_GENERAL to be able to texture from framebuffers without transitioning them? -#ifdef VULKAN_USE_GENERAL_LAYOUT_FOR_COLOR - image2.imageLayout = VK_IMAGE_LAYOUT_GENERAL; -#else image2.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -#endif image2.imageView = tex2; image2.sampler = sampler2; writes[n].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; @@ -376,7 +368,7 @@ VkPipeline Vulkan2D::GetPipeline(VkRenderPass rp, VkShaderModule vs, VkShaderMod VkShaderModule CompileShaderModule(VulkanContext *vulkan, VkShaderStageFlagBits stage, const char *code, std::string *error) { std::vector spirv; - bool success = GLSLtoSPV(stage, code, spirv, error); + bool success = GLSLtoSPV(stage, code, GLSLVariant::VULKAN, spirv, error); if (!error->empty()) { if (success) { ERROR_LOG(G3D, "Warnings in shader compilation!"); diff --git a/GPU/ge_constants.h b/GPU/ge_constants.h index 5facb4ad05..92c06c3f1d 100644 --- a/GPU/ge_constants.h +++ b/GPU/ge_constants.h @@ -287,6 +287,9 @@ enum GEBufferFormat }; const char *GeBufferFormatToString(GEBufferFormat fmt); +inline bool IsGeBufferFormat16BitColor(GEBufferFormat fmt) { + return (int)fmt < 3; +} #define GE_VTYPE_TRANSFORM (0<<23) #define GE_VTYPE_THROUGH (1<<23) @@ -416,12 +419,33 @@ enum GETextureFormat GE_TFMT_DXT5 = 10, }; -const char *GeTextureFormatToString(GETextureFormat fmt); -inline bool IsClutFormat(GETextureFormat fmt) { - return fmt == GE_TFMT_CLUT4 || fmt == GE_TFMT_CLUT8 || fmt == GE_TFMT_CLUT16 || fmt == GE_TFMT_CLUT32; +const char *GeTextureFormatToString(GETextureFormat tfmt); +inline bool IsClutFormat(GETextureFormat tfmt) { + return tfmt == GE_TFMT_CLUT4 || tfmt == GE_TFMT_CLUT8 || tfmt == GE_TFMT_CLUT16 || tfmt == GE_TFMT_CLUT32; } -inline bool IsDXTFormat(GETextureFormat fmt) { - return fmt == GE_TFMT_DXT1 || fmt == GE_TFMT_DXT3 || fmt == GE_TFMT_DXT5; +inline bool IsDXTFormat(GETextureFormat tfmt) { + return tfmt == GE_TFMT_DXT1 || tfmt == GE_TFMT_DXT3 || tfmt == GE_TFMT_DXT5; +} +inline bool IsTextureFormatBufferCompatible(GETextureFormat tfmt) { + return (int)tfmt < 4; +} +inline bool IsBufferFormat16Bit(GEBufferFormat bfmt) { + return (int)bfmt < 3; +} +inline bool IsTextureFormat16Bit(GETextureFormat tfmt) { + return (int)tfmt < 3; +} +inline bool TextureFormatMatchesBufferFormat(GETextureFormat fmt, GEBufferFormat bfmt) { + // First four matches perfectly. + if ((int)fmt < 4) { + return (int)fmt == (int)bfmt; + } else { + return false; + } +} +// only applicable if IsTextureFormatBufferCompatible(fmt) +inline GEBufferFormat TextureFormatToBufferFormat(GETextureFormat bfmt) { + return (GEBufferFormat)(int)bfmt; } enum GETexLevelMode { diff --git a/Qt/QtHost.h b/Qt/QtHost.h index e0c7ef4b7e..2aa9bc1870 100644 --- a/Qt/QtHost.h +++ b/Qt/QtHost.h @@ -92,6 +92,8 @@ public: NativeMessageReceived(message.c_str(), value.c_str()); } + void NotifySwitchUMDUpdated() override {} + private: std::string SymbolMapFilename(std::string currentFilename); MainWindow* mainWindow; diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index a38e7f1fa3..38258740ce 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -477,6 +477,7 @@ void MainWindow::SetWindowScale(int zoom) { #else resize(g_Config.iWindowWidth, g_Config.iWindowHeight); #endif + updateMenus(); } void MainWindow::SetGameTitle(QString text) @@ -603,7 +604,7 @@ void MainWindow::createMenus() MenuTree* windowMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("&Window size")); windowGroup = new MenuActionGroup(this, windowMenu, SLOT(windowGroup_triggered(QAction *)), QStringList() << "&1x" << "&2x" << "&3x" << "&4x" << "&5x" << "&6x" << "&7x" << "&8x" << "&9x" << "1&0x", - QList() << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9); + QList() << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10); MenuTree* renderingModeMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("Rendering m&ode")); renderingModeGroup = new MenuActionGroup(this, renderingModeMenu, SLOT(renderingModeGroup_triggered(QAction *)), diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index feea1bbb1e..5145909f51 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -496,10 +496,17 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Active") : di->T("Inactive"))); int highp_int_min = gl_extensions.range[1][5][0]; int highp_int_max = gl_extensions.range[1][5][1]; + int highp_float_min = gl_extensions.range[1][2][0]; + int highp_float_max = gl_extensions.range[1][2][1]; if (highp_int_max != 0) { - char highp_int_range[512]; - snprintf(highp_int_range, sizeof(highp_int_range), "Highp int range: %d-%d", highp_int_min, highp_int_max); - deviceSpecs->Add(new InfoItem(si->T("High precision int range"), highp_int_range)); + char temp[512]; + snprintf(temp, sizeof(temp), "Highp int range: %d-%d", highp_int_min, highp_int_max); + deviceSpecs->Add(new InfoItem(si->T("High precision int range"), temp)); + } + if (highp_float_max != 0) { + char temp[512]; + snprintf(temp, sizeof(temp), "Highp float range: %d-%d", highp_int_min, highp_int_max); + deviceSpecs->Add(new InfoItem(si->T("High precision float range"), temp)); } } deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); @@ -538,16 +545,6 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("Refresh rate"), StringFromFormat("%0.3f Hz", (float)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE)))); #endif -#if 0 - // For debugging, DO NOT translate - deviceSpecs->Add(new InfoItem("Resolution1", - StringFromFormat("dp: %dx%d px: %dx%d dpi_s: %0.1fx%0.1f", - dp_xres, dp_yres, pixel_xres, pixel_yres, g_dpi_scale_x, g_dpi_scale_y))); - deviceSpecs->Add(new InfoItem("Resolution2", - StringFromFormat("dpi_s_r: %0.1fx%0.1f px_in_dp: %0.1fx%0.1f", - g_dpi_scale_real_x, g_dpi_scale_real_y, pixel_in_dps_x, pixel_in_dps_y))); -#endif - deviceSpecs->Add(new ItemHeader(si->T("Version Information"))); std::string apiVersion; if (GetGPUBackend() == GPUBackend::OPENGL) { @@ -564,7 +561,7 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("API Version"), apiVersion)); deviceSpecs->Add(new InfoItem(si->T("Shading Language"), draw->GetInfoString(InfoField::SHADELANGVERSION))); -#ifdef __ANDROID__ +#if PPSSPP_PLATFORM(ANDROID) std::string moga = System_GetProperty(SYSPROP_MOGA_VERSION); if (moga.empty()) { moga = si->T("(none detected)"); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index c4f9cdfc33..5bd31c534e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1094,7 +1094,7 @@ void EmuScreen::CreateViews() { saveStatePreview_->SetVisibility(V_GONE); saveStatePreview_->SetCanBeFocused(false); root_->Add(saveStatePreview_); - root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h))); + onScreenMessagesView_ = root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h))); GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT))); TextView *loadingTextView = root_->Add(new TextView(sc->T(PSP_GetLoading()), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 40, true))); @@ -1165,11 +1165,12 @@ UI::EventReturn EmuScreen::OnChat(UI::EventParams& params) { } void EmuScreen::update() { - UIScreen::update(); + onScreenMessagesView_->SetVisibility(g_Config.bShowOnScreenMessages ? UI::Visibility::V_VISIBLE : UI::Visibility::V_GONE); - if (bootPending_) + if (bootPending_) { bootGame(gamePath_); + } // Simply forcibly update to the current screen size every frame. Doesn't cost much. // If bounds is set to be smaller than the actual pixel resolution of the display, respect that. @@ -1312,7 +1313,7 @@ ABI: %s int x = 20; int y = 50; draw2d->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF); - y += 100; + y += 140; if (info.type == ExceptionType::MEMORY) { snprintf(statbuf, sizeof(statbuf), R"( @@ -1333,7 +1334,13 @@ PC: %08x)", info.address, info.pc); draw2d->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF); - y += 120; + y += 180; + } else { + snprintf(statbuf, sizeof(statbuf), R"( +BREAK +)"); + draw2d->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF); + y += 180; } std::string kernelState = __KernelStateSummary(); @@ -1597,7 +1604,7 @@ void EmuScreen::renderUI() { } if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowGpuProfile) { - DrawProfilerVis(ctx, gpu); + DrawGPUProfilerVis(ctx, gpu); } #endif diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 77a072f433..69befa43b0 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -30,6 +30,7 @@ struct AxisInput; class AsyncImageFileView; +class OnScreenMessagesView; class EmuScreen : public UIScreen { public: @@ -106,6 +107,8 @@ private: UI::TextView *loadingTextView_ = nullptr; UI::Button *cardboardDisableButton_ = nullptr; + OnScreenMessagesView *onScreenMessagesView_ = nullptr; + bool autoRotatingAnalogCW_ = false; bool autoRotatingAnalogCCW_ = false; }; diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index 56547cd8b9..297d1e55e8 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -1,9 +1,10 @@ #include "GPUDriverTestScreen.h" #include "Common/Data/Text/I18n.h" #include "Common/UI/View.h" +#include "Common/GPU/Shader.h" static const std::vector fsDiscard = { - {Draw::ShaderLanguage::GLSL_ES_200, + {ShaderLanguage::GLSL_1xx, R"( #ifdef GL_ES precision lowp float; @@ -23,7 +24,7 @@ static const std::vector fsDiscard = { gl_FragColor = color; })" }, - {Draw::ShaderLanguage::GLSL_VULKAN, + {ShaderLanguage::GLSL_VULKAN, R"(#version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable @@ -101,7 +102,7 @@ void GPUDriverTestScreen::DiscardTest() { // Create the special shader module. - discardFragShader_ = CreateShader(draw, Draw::ShaderStage::FRAGMENT, fsDiscard); + discardFragShader_ = CreateShader(draw, ShaderStage::Fragment, fsDiscard); InputLayout *inputLayout = ui_draw2d.CreateInputLayout(draw); BlendState *blendOff = draw->CreateBlendState({ false, 0xF }); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index ffb993d639..16f7beb3c3 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -640,8 +640,8 @@ void GameSettingsScreen::CreateViews() { std::vector micList = Microphone::getDeviceList(); if (micList.size() >= 1) { - audioSettings->Add(new ItemHeader(gr->T("Microphone"))); - PopupMultiChoiceDynamic *MicChoice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sMicDevice, gr->T("Microphone Device"), micList, nullptr, screenManager())); + audioSettings->Add(new ItemHeader(a->T("Microphone"))); + PopupMultiChoiceDynamic *MicChoice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sMicDevice, a->T("Microphone Device"), micList, nullptr, screenManager())); MicChoice->OnChoice.Handle(this, &GameSettingsScreen::OnMicDeviceChange); } @@ -810,7 +810,7 @@ void GameSettingsScreen::CreateViews() { networkingSettings->Add(new ItemHeader(n->T("AdHoc Server"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in PRO Adhoc Server", "Enable built-in PRO Adhoc Server"))); - networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address (localhost = multiple instance)"), (const char*)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress); + networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address", "Change proAdhocServer Address (localhost = multiple instance)"), (const char*)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress); networkingSettings->Add(new ItemHeader(n->T("UPnP (port-forwarding)"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)"))); @@ -869,9 +869,9 @@ void GameSettingsScreen::CreateViews() { qc5->SetEnabledPtr(&g_Config.bEnableQuickChat); #endif - networkingSettings->Add(new ItemHeader(n->T("Misc (default = compatiblity)"))); + networkingSettings->Add(new ItemHeader(n->T("Misc", "Misc (default = compatibility)"))); networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset (0 = PSP compatibility)"), 100, screenManager())); - networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 1, 15000, n->T("Minimum Timeout", "Minimum Timeout (override low latency in ms)"), 100, screenManager())); + networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 0, 15000, n->T("Minimum Timeout", "Minimum Timeout (override in ms, 0 = default)"), 50, screenManager())); networkingSettings->Add(new CheckBox(&g_Config.bTCPNoDelay, n->T("TCP No Delay", "TCP No Delay (faster TCP)"))); networkingSettings->Add(new CheckBox(&g_Config.bForcedFirstConnect, n->T("Forced First Connect", "Forced First Connect (faster Connect)"))); @@ -903,7 +903,7 @@ void GameSettingsScreen::CreateViews() { if (!g_Config.bSimpleUI) { systemSettings->Add(new ItemHeader(sy->T("UI Language"))); // Should be renamed "UI"? systemSettings->Add(new Choice(dev->T("Language", "Language")))->OnClick.Handle(this, &GameSettingsScreen::OnLanguage); - systemSettings->Add(new CheckBox(&g_Config.bUISound, dev->T("UI Sound"))); + systemSettings->Add(new CheckBox(&g_Config.bUISound, sy->T("UI Sound"))); systemSettings->Add(new ItemHeader(sy->T("Help the PPSSPP team"))); enableReports_ = Reporting::IsEnabled(); @@ -1701,6 +1701,7 @@ void DeveloperToolsScreen::CreateViews() { list->Add(allowDebugger)->OnClick.Handle(this, &DeveloperToolsScreen::OnRemoteDebugger); allowDebugger->SetEnabledPtr(&canAllowDebugger_); + list->Add(new CheckBox(&g_Config.bShowOnScreenMessages, dev->T("Show on-screen messages"))); list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged); list->Add(new CheckBox(&g_Config.bLogFrameDrops, dev->T("Log Dropped Frame Statistics"))); list->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLogConfig); diff --git a/UI/HostTypes.h b/UI/HostTypes.h index 134c808862..c801859b07 100644 --- a/UI/HostTypes.h +++ b/UI/HostTypes.h @@ -53,4 +53,6 @@ public: void SendUIMessage(const std::string &message, const std::string &value) override { NativeMessageReceived(message.c_str(), value.c_str()); } + + void NotifySwitchUMDUpdated() override {} }; diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index 0f5f79551d..3f871e348f 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -116,12 +116,16 @@ void InstallZipScreen::update() { progressBar_->SetVisibility(V_VISIBLE); progressBar_->SetProgress(g_GameManager.GetCurrentInstallProgressPercentage()); } - backChoice_->SetEnabled(false); + if (backChoice_) { + backChoice_->SetEnabled(false); + } } else { if (progressBar_) { progressBar_->SetVisibility(V_GONE); } - backChoice_->SetEnabled(true); + if (backChoice_) { + backChoice_->SetEnabled(true); + } std::string err = g_GameManager.GetInstallError(); if (!err.empty()) { if (doneView_) diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index e8d56b3ab6..bf2b16ea49 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -419,6 +419,9 @@ + + + @@ -536,6 +539,9 @@ + + + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 7576f5735f..c0fb4df3ec 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -363,6 +363,15 @@ Render\Text + + GPU + + + GPU + + + GPU + @@ -662,6 +671,15 @@ Data\Collections + + GPU + + + GPU + + + GPU + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index be5fdc1a1f..e241137cf6 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -381,15 +381,16 @@ + + - @@ -401,25 +402,22 @@ + - - - - @@ -440,15 +438,16 @@ + + - @@ -463,10 +462,10 @@ + - @@ -474,14 +473,11 @@ - - - @@ -527,4 +523,4 @@ - + \ No newline at end of file diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index 261335da46..0c5f3538bb 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -11,7 +11,6 @@ - @@ -29,7 +28,6 @@ - @@ -37,14 +35,11 @@ - - - @@ -59,6 +54,9 @@ + + + @@ -71,7 +69,6 @@ - @@ -86,22 +83,18 @@ - - - - @@ -118,5 +111,8 @@ + + + - + \ No newline at end of file diff --git a/UWP/UWPHost.h b/UWP/UWPHost.h index 3c70db9991..ee49d0cba5 100644 --- a/UWP/UWPHost.h +++ b/UWP/UWPHost.h @@ -40,6 +40,8 @@ public: void NotifyUserMessage(const std::string &message, float duration = 1.0f, u32 color = 0x00FFFFFF, const char *id = nullptr) override; + void NotifySwitchUMDUpdated() override {} + GraphicsContext *GetGraphicsContext() { return nullptr; } private: diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index ec5cedcb3f..2b74b8f1b5 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -21,6 +21,7 @@ #include #include "Common/Data/Text/Parsers.h" +#include "Common/Data/Encoding/Utf8.h" #include "Common/ColorConv.h" #include "Common/StringUtils.h" #include "Core/Config.h" @@ -280,10 +281,10 @@ void CGEDebugger::SetupPreviews() { } } -void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, wchar_t desc[256]) { +void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, char desc[256]) { if (showClut_) { // In this case, we're showing the texture here. - _snwprintf(desc, 256, L"Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_)); + snprintf(desc, 256, "Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_)); return; } @@ -291,24 +292,24 @@ void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, wchar_t desc[25 switch (PrimaryDisplayType(fbTabs->CurrentTabIndex())) { case PRIMARY_FRAMEBUF: - _snwprintf(desc, 256, L"Color: 0x%08x (%dx%d) fmt %i", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight(), state.FrameBufFormat()); + snprintf(desc, 256, "Color: 0x%08x (%dx%d) fmt %s", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight(), GeBufferFormatToString(state.FrameBufFormat())); break; case PRIMARY_DEPTHBUF: - _snwprintf(desc, 256, L"Depth: 0x%08x (%dx%d)", state.getDepthBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight()); + snprintf(desc, 256, "Depth: 0x%08x (%dx%d)", state.getDepthBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight()); break; case PRIMARY_STENCILBUF: - _snwprintf(desc, 256, L"Stencil: 0x%08x (%dx%d)", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight()); + snprintf(desc, 256, "Stencil: 0x%08x (%dx%d)", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight()); break; } } -void CGEDebugger::DescribeSecondPreview(const GPUgstate &state, wchar_t desc[256]) { +void CGEDebugger::DescribeSecondPreview(const GPUgstate &state, char desc[256]) { if (showClut_) { - _snwprintf(desc, 256, L"CLUT: 0x%08x (%d)", state.getClutAddress(), state.getClutPaletteFormat()); + snprintf(desc, 256, "CLUT: 0x%08x (%d)", state.getClutAddress(), state.getClutPaletteFormat()); } else { - _snwprintf(desc, 256, L"Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_)); + snprintf(desc, 256, "Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_)); } } @@ -427,9 +428,12 @@ void CGEDebugger::UpdatePrimaryPreview(const GPUgstate &state) { primaryWindow->SetFlags(flags); primaryWindow->Draw(primaryBuffer_->GetData(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight(), primaryBuffer_->GetFlipped(), fmt); - wchar_t desc[256]; + char desc[256]; + wchar_t w_desc[256]; DescribePrimaryPreview(state, desc); - SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, desc); + ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc); + + SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, w_desc); } else if (primaryWindow != nullptr) { primaryWindow->Clear(); primaryBuffer_ = nullptr; @@ -465,9 +469,11 @@ void CGEDebugger::UpdateSecondPreview(const GPUgstate &state) { secondWindow->Draw(secondBuffer_->GetData(), secondBuffer_->GetStride(), secondBuffer_->GetHeight(), secondBuffer_->GetFlipped(), fmt); } - wchar_t desc[256]; + char desc[256]; DescribeSecondPreview(state, desc); - SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, desc); + wchar_t w_desc[256]; + ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc); + SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, w_desc); } else if (secondWindow != nullptr) { secondWindow->Clear(); secondBuffer_ = nullptr; @@ -487,7 +493,7 @@ void CGEDebugger::PrimaryPreviewHover(int x, int y) { SetupPreviews(); - wchar_t desc[256] = {0}; + char desc[256] = {0}; if (!primaryWindow->HasTex()) { desc[0] = 0; @@ -504,7 +510,10 @@ void CGEDebugger::PrimaryPreviewHover(int x, int y) { DescribePixel(pix, primaryBuffer_->GetFormat(), x, y, desc); } - SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, desc); + wchar_t w_desc[256]; + ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc); + + SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, w_desc); } void CGEDebugger::SecondPreviewHover(int x, int y) { @@ -512,7 +521,7 @@ void CGEDebugger::SecondPreviewHover(int x, int y) { return; } - wchar_t desc[256] = {0}; + char desc[256] = {0}; if (!secondWindow->HasTex()) { desc[0] = 0; @@ -532,11 +541,12 @@ void CGEDebugger::SecondPreviewHover(int x, int y) { DescribePixel(pix, secondBuffer_->GetFormat(), x, y, desc); } } - - SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, desc); + wchar_t w_desc[256]; + ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc); + SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, w_desc); } -void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]) { +void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) { switch (fmt) { case GPU_DBG_FORMAT_565: case GPU_DBG_FORMAT_565_REV: @@ -552,18 +562,18 @@ void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, break; case GPU_DBG_FORMAT_16BIT: - _snwprintf(desc, 256, L"%d,%d: %d / %f", x, y, pix, pix * (1.0f / 65535.0f)); + snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 65535.0f)); break; case GPU_DBG_FORMAT_8BIT: - _snwprintf(desc, 256, L"%d,%d: %d / %f", x, y, pix, pix * (1.0f / 255.0f)); + snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 255.0f)); break; case GPU_DBG_FORMAT_24BIT_8X: { DepthScaleFactors depthScale = GetDepthScaleFactors(); // These are only ever going to be depth values, so let's also show scaled to 16 bit. - _snwprintf(desc, 256, L"%d,%d: %d / %f / %f", x, y, pix & 0x00FFFFFF, (pix & 0x00FFFFFF) * (1.0f / 16777215.0f), depthScale.Apply((pix & 0x00FFFFFF) * (1.0f / 16777215.0f))); + snprintf(desc, 256, "%d,%d: %d / %f / %f", x, y, pix & 0x00FFFFFF, (pix & 0x00FFFFFF) * (1.0f / 16777215.0f), depthScale.Apply((pix & 0x00FFFFFF) * (1.0f / 16777215.0f))); break; } @@ -572,18 +582,18 @@ void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, // These are only ever going to be depth values, so let's also show scaled to 16 bit. int z24 = pix & 0x00FFFFFF; int z16 = z24 - 0x800000 + 0x8000; - _snwprintf(desc, 256, L"%d,%d: %d / %f", x, y, z16, z16 * (1.0f / 65535.0f)); + snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, z16 * (1.0f / 65535.0f)); } break; case GPU_DBG_FORMAT_24X_8BIT: - _snwprintf(desc, 256, L"%d,%d: %d / %f", x, y, (pix >> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f)); + snprintf(desc, 256, "%d,%d: %d / %f", x, y, (pix >> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f)); break; case GPU_DBG_FORMAT_FLOAT: { float pixf = *(float *)&pix; DepthScaleFactors depthScale = GetDepthScaleFactors(); - _snwprintf(desc, 256, L"%d,%d: %f / %f", x, y, pixf, depthScale.Apply(pixf)); + snprintf(desc, 256, "%d,%d: %f / %f", x, y, pixf, depthScale.Apply(pixf)); break; } @@ -598,16 +608,16 @@ void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, int z16_2 = factors.Apply(z); - _snwprintf(desc, 256, L"%d,%d: %d / %f", x, y, z16, (z - 0.5 + (1.0 / 512.0)) * 256.0); + snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, (z - 0.5 + (1.0 / 512.0)) * 256.0); } break; default: - _snwprintf(desc, 256, L"Unexpected format"); + snprintf(desc, 256, "Unexpected format"); } } -void CGEDebugger::DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]) { +void CGEDebugger::DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) { u32 r = -1, g = -1, b = -1, a = -1; switch (fmt) { @@ -671,11 +681,11 @@ void CGEDebugger::DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, in break; default: - _snwprintf(desc, 256, L"Unexpected format"); + snprintf(desc, 256, "Unexpected format"); return; } - _snwprintf(desc, 256, L"%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a); + snprintf(desc, 256, "%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a); } void CGEDebugger::UpdateTextureLevel(int level) { diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index d4484174a3..c487c073c0 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -76,14 +76,14 @@ private: void UpdateSize(WORD width, WORD height); void SavePosition(); void UpdateTextureLevel(int level); - void DescribePrimaryPreview(const GPUgstate &state, wchar_t desc[256]); - void DescribeSecondPreview(const GPUgstate &state, wchar_t desc[256]); + void DescribePrimaryPreview(const GPUgstate &state, char desc[256]); + void DescribeSecondPreview(const GPUgstate &state, char desc[256]); void PrimaryPreviewHover(int x, int y); void SecondPreviewHover(int x, int y); void PreviewExport(const GPUDebugBuffer *buffer); + void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); + void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void ShowReplacementFilename(); - void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]); - void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, wchar_t desc[256]); u32 TexturePreviewFlags(const GPUgstate &state); diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index 8f93ebc88d..3275b64277 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -74,9 +74,6 @@ HRESULT D3D11Context::CreateTheDevice(IDXGIAdapter *adapter) { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1, }; const UINT numFeatureLevels = ARRAYSIZE(featureLevels); diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 765321c903..e919a10420 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -27,6 +27,7 @@ #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/Core.h" +#include "Core/Host.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Text/I18n.h" #include "UI/OnScreenDisplay.h" @@ -420,6 +421,10 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { return false; } + draw_->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) { + host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback"); + }, nullptr); + // These are auto-reset events. pauseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); resumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index f10b631a43..36a579a305 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -1010,6 +1010,10 @@ namespace MainWindow InputDevice::BeginPolling(); break; + case WM_USER_SWITCHUMD_UPDATED: + UpdateSwitchUMD(); + break; + case WM_MENUSELECT: // Called when a menu is opened. Also when an item is selected, but meh. UpdateMenus(true); diff --git a/Windows/MainWindow.h b/Windows/MainWindow.h index a79770cbb3..c72ae17917 100644 --- a/Windows/MainWindow.h +++ b/Windows/MainWindow.h @@ -21,6 +21,7 @@ namespace MainWindow WM_USER_BROWSE_BOOT_DONE = WM_USER + 104, WM_USER_TOGGLE_FULLSCREEN = WM_USER + 105, WM_USER_RESTART_EMUTHREAD = WM_USER + 106, + WM_USER_SWITCHUMD_UPDATED = WM_USER + 107 }; enum { @@ -67,6 +68,7 @@ namespace MainWindow void Close(); void UpdateMenus(bool isMenuSelect = false); void UpdateCommands(); + void UpdateSwitchUMD(); void SetWindowTitle(const wchar_t *title); void Redraw(); HWND GetHWND(); diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 8d42636f53..956c92f543 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -1399,6 +1399,13 @@ namespace MainWindow { TranslateMenuItem(menu, ID_TOGGLE_BREAK, L"\tF8", isPaused ? "Run" : "Break"); } + void UpdateSwitchUMD() { + HMENU menu = GetMenu(GetHWND()); + GlobalUIState state = GetUIState(); + UINT umdSwitchEnable = state == UISTATE_INGAME && getUMDReplacePermit() ? MF_ENABLED : MF_GRAYED; + EnableMenuItem(menu, ID_EMULATION_SWITCH_UMD, umdSwitchEnable); + } + // Message handler for about box. LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 55a21f5a1d..bef161003e 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -1704,6 +1704,7 @@ + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index ffbdc51429..141d03a3ca 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -729,5 +729,8 @@ Other Platforms\libretro + + Other Platforms\libretro + \ No newline at end of file diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index 210344033f..d7b3d93396 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -407,3 +407,7 @@ void WindowsHost::NotifyUserMessage(const std::string &message, float duration, void WindowsHost::SendUIMessage(const std::string &message, const std::string &value) { NativeMessageReceived(message.c_str(), value.c_str()); } + +void WindowsHost::NotifySwitchUMDUpdated() { + PostMessage(mainWindow_, MainWindow::WM_USER_SWITCHUMD_UPDATED, 0, 0); +} diff --git a/Windows/WindowsHost.h b/Windows/WindowsHost.h index 581a8064d3..492d47a9db 100644 --- a/Windows/WindowsHost.h +++ b/Windows/WindowsHost.h @@ -63,6 +63,8 @@ public: void NotifyUserMessage(const std::string &message, float duration = 1.0f, u32 color = 0x00FFFFFF, const char *id = nullptr) override; void SendUIMessage(const std::string &message, const std::string &value) override; + void NotifySwitchUMDUpdated() override; + GraphicsContext *GetGraphicsContext() { return gfx_; } private: diff --git a/Windows/XinputDevice.cpp b/Windows/XinputDevice.cpp index b93c8bf4c7..724e983819 100644 --- a/Windows/XinputDevice.cpp +++ b/Windows/XinputDevice.cpp @@ -158,7 +158,7 @@ inline float Clampf(float val, float min, float max) { } inline float Signf(float val) { - return (0.0f < val) - (val < 0.0f); + return (float)((0.0f < val) - (val < 0.0f)); } inline float LinearMapf(float val, float a0, float a1, float b0, float b1) { diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 11e2682563..964747a804 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -26,6 +26,7 @@ + input_sample_rate = sampleRate; + INFO_LOG(AUDIO, "AndroidAudio_Recording_SetSampleRate=%d", sampleRate); + return true; +} + +bool AndroidAudio_Recording_Start(AndroidAudioState *state) { + if (!state) { + ERROR_LOG(AUDIO, "AndroidAudioState not initialized, cannot start recording!"); + return false; + } + state->input_enable = 1; + if (!state->ctx) { + ERROR_LOG(AUDIO, "OpenSLContext not initialized, cannot start recording!"); + return false; + } + state->ctx->AudioRecord_Start(state->input_sample_rate); + INFO_LOG(AUDIO, "AndroidAudio_Recording_Start"); + return true; +} + +bool AndroidAudio_Recording_Stop(AndroidAudioState *state) { + if (!state) { + ERROR_LOG(AUDIO, "AndroidAudioState not initialized, cannot stop recording!"); + return false; + } + if (!state->ctx) { + ERROR_LOG(AUDIO, "OpenSLContext not initialized, cannot stop recording!"); + return false; + } + state->input_enable = 0; + state->input_sample_rate = 0; + state->ctx->AudioRecord_Stop(); + INFO_LOG(AUDIO, "AndroidAudio_Recording_Stop"); + return true; +} + +bool AndroidAudio_Recording_State(AndroidAudioState *state) { + if (!state) { + return false; + } + return state->input_enable; +} + bool AndroidAudio_Resume(AndroidAudioState *state) { if (!state) { ERROR_LOG(AUDIO, "Audio was shutdown, cannot resume!"); @@ -44,6 +96,9 @@ bool AndroidAudio_Resume(AndroidAudioState *state) { delete state->ctx; state->ctx = nullptr; } + if (state->input_enable) { + state->ctx->AudioRecord_Start(state->input_sample_rate); + } return init_retval; } return false; diff --git a/android/jni/AndroidAudio.h b/android/jni/AndroidAudio.h index a3d1ab8ee8..3a8fe5e33c 100644 --- a/android/jni/AndroidAudio.h +++ b/android/jni/AndroidAudio.h @@ -8,6 +8,8 @@ class AudioContext { public: AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate); virtual bool Init() { return false; } + virtual int AudioRecord_Start(int sampleRate) { return false; }; + virtual int AudioRecord_Stop() { return false; }; virtual ~AudioContext() {} protected: @@ -21,6 +23,10 @@ struct AndroidAudioState; // It's okay for optimalFramesPerBuffer and optimalSampleRate to be 0. Defaults will be used. AndroidAudioState *AndroidAudio_Init(AndroidAudioCallback cb, int optimalFramesPerBuffer, int optimalSampleRate); +bool AndroidAudio_Recording_SetSampleRate(AndroidAudioState *state, int sampleRate); +bool AndroidAudio_Recording_Start(AndroidAudioState *state); +bool AndroidAudio_Recording_Stop(AndroidAudioState *state); +bool AndroidAudio_Recording_State(AndroidAudioState *state); bool AndroidAudio_Pause(AndroidAudioState *state); bool AndroidAudio_Resume(AndroidAudioState *state); bool AndroidAudio_Shutdown(AndroidAudioState *state); diff --git a/android/jni/OpenSLContext.cpp b/android/jni/OpenSLContext.cpp index 77ccfde2eb..47841567e0 100644 --- a/android/jni/OpenSLContext.cpp +++ b/android/jni/OpenSLContext.cpp @@ -11,6 +11,32 @@ #include "Common/Log.h" #include "OpenSLContext.h" +#include "Core/HLE/sceUsbMic.h" + +void OpenSLContext::bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { + OpenSLContext *ctx = (OpenSLContext *)context; + SLresult result; + + SLuint32 recordsState; + result = (*ctx->recorderRecord)->GetRecordState(ctx->recorderRecord, &recordsState); + if(result != SL_RESULT_SUCCESS) { + ERROR_LOG(AUDIO, "GetRecordState error: %d", result); + return; + } + + Microphone::addAudioData((uint8_t*) ctx->recordBuffer[ctx->activeRecordBuffer], ctx->recordBufferSize); + + if (recordsState == SL_RECORDSTATE_RECORDING) { + result = (*ctx->recorderBufferQueue)->Enqueue(ctx->recorderBufferQueue, ctx->recordBuffer[ctx->activeRecordBuffer], ctx->recordBufferSize); + if (result != SL_RESULT_SUCCESS) { + ERROR_LOG(AUDIO, "Enqueue error: %d", result); + } + } + + ctx->activeRecordBuffer += 1; // Switch buffer + if (ctx->activeRecordBuffer == NUM_BUFFERS) + ctx->activeRecordBuffer = 0; +} // This callback handler is called every time a buffer finishes playing. // The documentation available is very unclear about how to best manage buffers. @@ -101,7 +127,8 @@ bool OpenSLContext::Init() { // create audio player const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; - result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, + sizeof(ids)/sizeof(ids[0]), ids, req); if (result != SL_RESULT_SUCCESS) { ERROR_LOG(AUDIO, "OpenSL: CreateAudioPlayer failed: %d", (int)result); (*outputMixObject)->Destroy(outputMixObject); @@ -145,6 +172,102 @@ bool OpenSLContext::Init() { return true; } +int OpenSLContext::AudioRecord_Start(int sampleRate) { + SLresult result; + + // configure audio source + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + // configure audio sink + SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + 1, + (SLuint32) sampleRate * 1000, // The constants such as SL_SAMPLINGRATE_44_1 are 44100000 + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + // create audio recorder + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, + sizeof(id)/sizeof(id[0]), id, req); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "CreateAudioRecorder failed: %d", result); + return false; + } + + // realize the audio recorder + result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "Realize failed: %d", result); + return false; + } + + // get the record interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "GetInterface(record) failed: %d", result); + return false; + } + + // get the buffer queue interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, (void*) &recorderBufferQueue); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "GetInterface(queue interface) failed: %d", result); + return false; + } + + // register callback on the buffer queue + result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, &bqRecorderCallback, this); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "RegisterCallback failed: %d", result); + return false; + } + + recordBufferSize = (44100 * 20 / 1000 * 2); + for (int i = 0; i < NUM_BUFFERS; i++) { + recordBuffer[i] = new short[recordBufferSize]; + } + for (int i = 0; i < NUM_BUFFERS; i++) { + result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer[i], recordBufferSize); + if (SL_RESULT_SUCCESS != result) { + ERROR_LOG(AUDIO, "Enqueue failed: %d", result); + return false; + } + } + + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); + _assert_(SL_RESULT_SUCCESS == result); + + return true; +} + +int OpenSLContext::AudioRecord_Stop() { + if (recorderRecord != nullptr) { + SLresult result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); + _assert_(SL_RESULT_SUCCESS == result); + } + if (recorderObject != nullptr) { + (*recorderObject)->Destroy(recorderObject); + recorderObject = nullptr; + recorderRecord = nullptr; + recorderBufferQueue = nullptr; + } + if (recordBuffer[0] != nullptr) { + delete [] recordBuffer[0]; + delete [] recordBuffer[1]; + recordBuffer[0] = nullptr; + recordBuffer[1] = nullptr; + } + return true; +} + // shut down the native audio system OpenSLContext::~OpenSLContext() { if (bqPlayerPlay) { @@ -172,6 +295,7 @@ OpenSLContext::~OpenSLContext() { (*outputMixObject)->Destroy(outputMixObject); outputMixObject = nullptr; } + AudioRecord_Stop(); INFO_LOG(AUDIO, "OpenSL: Shutdown - deleting engine object"); @@ -186,5 +310,4 @@ OpenSLContext::~OpenSLContext() { buffer[i] = nullptr; } INFO_LOG(AUDIO, "OpenSL: Shutdown - finished"); -} - +} diff --git a/android/jni/OpenSLContext.h b/android/jni/OpenSLContext.h index 3705f66b82..e95afa8502 100644 --- a/android/jni/OpenSLContext.h +++ b/android/jni/OpenSLContext.h @@ -10,25 +10,38 @@ public: OpenSLContext(AndroidAudioCallback cb, int framesPerBuffer, int sampleRate); bool Init() override; + int AudioRecord_Start(int sampleRate) override; + int AudioRecord_Stop() override; ~OpenSLContext(); private: + // Should be no reason to need more than two buffers, but make it clear in the code. + enum { + NUM_BUFFERS = 2, + }; + // engine interfaces SLObjectItf engineObject = nullptr; SLEngineItf engineEngine = nullptr; SLObjectItf outputMixObject = nullptr; + // audio recorder interfaces + SLObjectItf recorderObject = nullptr; + SLRecordItf recorderRecord = nullptr; + SLAndroidSimpleBufferQueueItf recorderBufferQueue = nullptr; + + int recordBufferSize = 0; + short *recordBuffer[NUM_BUFFERS]{}; + int activeRecordBuffer = 0; + + static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context); + // buffer queue player interfaces SLObjectItf bqPlayerObject = nullptr; SLPlayItf bqPlayerPlay = nullptr; SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = nullptr; SLVolumeItf bqPlayerVolume = nullptr; - // Should be no reason to need more than two buffers, but make it clear in the code. - enum { - NUM_BUFFERS = 2, - }; - // Double buffering. short *buffer[NUM_BUFFERS]{}; int curBuffer = 0; diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 54687fc1e8..82a69e8c27 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -82,6 +82,7 @@ struct JNIEnv {}; #include "Core/System.h" #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbGps.h" +#include "Core/Host.h" #include "Common/CPUDetect.h" #include "Common/Log.h" #include "UI/GameInfoCache.h" @@ -678,6 +679,26 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *, jclass) } } +extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *, jclass, jint sampleRate) { + AndroidAudio_Recording_SetSampleRate(g_audioState, sampleRate); +} + +extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *, jclass) { + AndroidAudio_Recording_Start(g_audioState); +} + +extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *, jclass) { + AndroidAudio_Recording_Stop(g_audioState); +} + +bool audioRecording_Available() { + return true; +} + +bool audioRecording_State() { + return AndroidAudio_Recording_State(g_audioState); +} + extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) { INFO_LOG(SYSTEM, "NativeApp.resume() - resuming audio"); AndroidAudio_Resume(g_audioState); @@ -757,6 +778,10 @@ extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, return false; } + graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) { + host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback"); + }, nullptr); + if (useCPUThread) { EmuThreadStart(); } else { @@ -775,6 +800,11 @@ extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, SystemToast("Graphics initialization failed. Quitting."); return false; } + + graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) { + host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback"); + }, nullptr); + graphicsContext->ThreadStart(); renderer_inited = true; } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 477d7b42cf..54ae0f04aa 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -122,10 +122,14 @@ public abstract class NativeActivity extends Activity { private static final String[] permissionsForCamera = { Manifest.permission.CAMERA }; + private static final String[] permissionsForMicrophone = { + Manifest.permission.RECORD_AUDIO + }; public static final int REQUEST_CODE_STORAGE_PERMISSION = 1; public static final int REQUEST_CODE_LOCATION_PERMISSION = 2; public static final int REQUEST_CODE_CAMERA_PERMISSION = 3; + public static final int REQUEST_CODE_MICROPHONE_PERMISSION = 4; // Functions for the app activity to override to change behaviour. @@ -224,6 +228,11 @@ public abstract class NativeActivity extends Activity { mCameraHelper.startCamera(); } break; + case REQUEST_CODE_MICROPHONE_PERMISSION: + if (permissionsGranted(permissions, grantResults)) { + NativeApp.audioRecording_Start(); + } + break; default: } } @@ -1289,6 +1298,16 @@ public abstract class NativeActivity extends Activity { } else if (mCameraHelper != null && params.equals("stopVideo")) { mCameraHelper.stopCamera(); } + } else if (command.equals("microphone_command")) { + if (params.startsWith("startRecording:")) { + int sampleRate = Integer.parseInt(params.replace("startRecording:", "")); + NativeApp.audioRecording_SetSampleRate(sampleRate); + if (!askForPermissions(permissionsForMicrophone, REQUEST_CODE_MICROPHONE_PERMISSION)) { + NativeApp.audioRecording_Start(); + } + } else if (params.equals("stopRecording")) { + NativeApp.audioRecording_Stop(); + } } else if (command.equals("uistate")) { Window window = this.getWindow(); if (params.equals("ingame")) { diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index 46598c8263..632c625bed 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -17,6 +17,10 @@ public class NativeApp { public static native void audioShutdown(); public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate); + public static native void audioRecording_SetSampleRate(int sampleRate); + public static native void audioRecording_Start(); + public static native void audioRecording_Stop(); + public static native void computeDesiredBackbufferDimensions(); public static native int getDesiredBackbufferWidth(); public static native int getDesiredBackbufferHeight(); diff --git a/assets/compat.ini b/assets/compat.ini index 267dd21712..9e74513856 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -668,6 +668,10 @@ ULUS10249 = true ULES00637 = true ULKS46126 = true +[IntraVRAMBlockTransferAllowCreateFB] +# Final Fantasy - Type 0 +NPJH50443 = true + # Note! This whole flag is disabled temporarily by appending "Disabled" to its name). See 7914 [YugiohSaveFixDisabled] # The cause of Yu-gi-oh series 's bad save (cannot save) are load "save status" and use cwcheat, @@ -895,3 +899,28 @@ ULES00235 = true ULJM05225 = true CPCS01043 = true ULUS10062 = true + +[MemstickFixedFree] +ULJM05571 = true +ULES01367 = true +NPEH00029 = true +ULUS10455 = true + +[DateLimited] +# Car Jack Streets - issue #12698 +NPUZ00043 = true +NPEZ00198 = true + +# This setting will go away in the near future, hopefully we can enable it +# for all or most games. +[ReinterpretFramebuffers] +# Outrun 2006: Coast to Coast - issue #11358 +ULES00262 = true +ULUS10064 = true +ULKS46087 = true + +# Colin McRae's DiRT 2? +# ULUS10471 = true +# ULJM05533 = true +# NPJH50006 = true + diff --git a/assets/flash0/font/jpn0.pgf b/assets/flash0/font/jpn0.pgf index 1553aecd86..34877884dc 100644 Binary files a/assets/flash0/font/jpn0.pgf and b/assets/flash0/font/jpn0.pgf differ diff --git a/assets/flash0/font/ltn0.pgf b/assets/flash0/font/ltn0.pgf index bf710feef0..0abc4fba29 100644 Binary files a/assets/flash0/font/ltn0.pgf and b/assets/flash0/font/ltn0.pgf differ diff --git a/assets/flash0/font/ltn1.pgf b/assets/flash0/font/ltn1.pgf index 7b9dede484..b5a7cd2313 100644 Binary files a/assets/flash0/font/ltn1.pgf and b/assets/flash0/font/ltn1.pgf differ diff --git a/assets/flash0/font/ltn2.pgf b/assets/flash0/font/ltn2.pgf index 9ee2763f85..bfd538eb0e 100644 Binary files a/assets/flash0/font/ltn2.pgf and b/assets/flash0/font/ltn2.pgf differ diff --git a/assets/flash0/font/ltn3.pgf b/assets/flash0/font/ltn3.pgf index 805c286330..2285f7703c 100644 Binary files a/assets/flash0/font/ltn3.pgf and b/assets/flash0/font/ltn3.pgf differ diff --git a/assets/flash0/font/ltn4.pgf b/assets/flash0/font/ltn4.pgf index 12b075101d..af45492b29 100644 Binary files a/assets/flash0/font/ltn4.pgf and b/assets/flash0/font/ltn4.pgf differ diff --git a/assets/flash0/font/ltn5.pgf b/assets/flash0/font/ltn5.pgf index d12e9eccc3..bc0b7f9649 100644 Binary files a/assets/flash0/font/ltn5.pgf and b/assets/flash0/font/ltn5.pgf differ diff --git a/assets/flash0/font/ltn6.pgf b/assets/flash0/font/ltn6.pgf index 0f626ddff2..c7b5f176d0 100644 Binary files a/assets/flash0/font/ltn6.pgf and b/assets/flash0/font/ltn6.pgf differ diff --git a/assets/flash0/font/ltn7.pgf b/assets/flash0/font/ltn7.pgf index ba9afda58c..530f98e9ea 100644 Binary files a/assets/flash0/font/ltn7.pgf and b/assets/flash0/font/ltn7.pgf differ diff --git a/assets/gamecontrollerdb.txt b/assets/gamecontrollerdb.txt index 8913052eaa..fcefb88ae7 100644 --- a/assets/gamecontrollerdb.txt +++ b/assets/gamecontrollerdb.txt @@ -196,3 +196,4 @@ bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,b 030000000d0f0000c100000011010000,HORI CO.,LTD. HORIPAD S,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 03000000ad1b000003f5000033050000,Hori Fighting Stick VX,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,-leftx:h0.8,+leftx:h0.2,-lefty:h0.1,+lefty:h0.4,lefttrigger:b6,righttrigger:b7, 03000000c82d00000260000011010000,8Bitdo SF30 Pro 8BitDo SN30 Pro+,platform:Linux,a:b1,b:b0,x:b4,y:b3,back:b10,guide:b2,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4, +060000007e0500000820000000000000,Nintendo Switch Combined Joy-Cons,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b9,guide:b11,start:b10,leftstick:b12,rightstick:b13,leftshoulder:b5,rightshoulder:b6,dpup:b14,dpdown:b15,dpleft:b16,dpright:b17,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b7,righttrigger:b8, diff --git a/assets/lang b/assets/lang index fd6f5bc01a..f6b285036a 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit fd6f5bc01afb419eacca538b425c01eb2a055393 +Subproject commit f6b285036acbf86cdbf9833142a4c4209d6709f8 diff --git a/assets/shaders/defaultshaders.ini b/assets/shaders/defaultshaders.ini index 6c01db9bd0..fae7e560db 100644 --- a/assets/shaders/defaultshaders.ini +++ b/assets/shaders/defaultshaders.ini @@ -17,7 +17,7 @@ Vertex=natural.vsh [NaturalA] Name=Natural Colors (No Blur) Fragment=naturalA.fsh -Vertex=natural.vsh +Vertex=naturalA.vsh [Vignette] Name=Vignette Author=Henrik @@ -180,6 +180,11 @@ SettingDefaultValue4=1.0 SettingMaxValue4=2.0 SettingMinValue4=0.1 SettingStep4=0.05 +[PSPColor] +Name=PSP Color +Author=hunterk, Pokefan531 (ported by jdgleaver) +Fragment=psp_color.fsh +Vertex=fxaa.vsh [Tex4xBRZ] Type=Texture Name=4xBRZ diff --git a/assets/shaders/naturalA.fsh b/assets/shaders/naturalA.fsh index 8e396464ca..201f8e7ea9 100644 --- a/assets/shaders/naturalA.fsh +++ b/assets/shaders/naturalA.fsh @@ -1,6 +1,6 @@ -// Natural Vision Shader with removed blur. +// Natural Vision Shader, modified to use in PPSSPP. -// by ShadX (Modded by SimoneT and Leopard20) +// by ShadX (Modded by SimoneT) // http://forums.ngemu.com/showthread.php?t=76098 #ifdef GL_ES @@ -9,10 +9,7 @@ precision mediump int; #endif uniform sampler2D sampler0; -varying vec4 v_texcoord0; -varying vec4 v_texcoord1; -varying vec4 v_texcoord2; -varying vec4 v_texcoord3; +varying vec2 v_texcoord0; const mat3 RGBtoYIQ = mat3(0.299, 0.596, 0.212, 0.587,-0.275,-0.523, @@ -28,7 +25,7 @@ void main() { vec3 c0,c1; -c0 = texture2D(sampler0,v_texcoord0.xy).xyz; +c0 = texture2D(sampler0,v_texcoord0).xyz; c1=RGBtoYIQ*c0; diff --git a/assets/shaders/naturalA.vsh b/assets/shaders/naturalA.vsh new file mode 100644 index 0000000000..ebad6e2724 --- /dev/null +++ b/assets/shaders/naturalA.vsh @@ -0,0 +1,10 @@ +attribute vec4 a_position; +attribute vec2 a_texcoord0; + +varying vec2 v_texcoord0; + +void main() +{ + gl_Position=a_position; + v_texcoord0=a_texcoord0; +} diff --git a/assets/shaders/psp_color.fsh b/assets/shaders/psp_color.fsh index aad99df883..2e597b3e39 100644 --- a/assets/shaders/psp_color.fsh +++ b/assets/shaders/psp_color.fsh @@ -46,7 +46,7 @@ void main() { // Apply colour correction vec3 screen = pow(texture2D(sampler0, v_texcoord0.xy).rgb, vec3(target_gamma)); - screen = clamp(screen, 0.0, 1.0); + // screen = clamp(screen, 0.0, 1.0); screen = pow( mat3(r, rg, rb, gr, g, gb, diff --git a/assets/shaders/psp_color.ini b/assets/shaders/psp_color.ini deleted file mode 100644 index 73cae04bb2..0000000000 --- a/assets/shaders/psp_color.ini +++ /dev/null @@ -1,6 +0,0 @@ -[PSP Color] -Name=PSP Color -Author=hunterk, Pokefan531 (ported by jdgleaver) -Fragment=psp_color.fsh -Vertex=psp_color.vsh -OutputResolution=True diff --git a/assets/shaders/psp_color.vsh b/assets/shaders/psp_color.vsh deleted file mode 100644 index 0353e17dae..0000000000 --- a/assets/shaders/psp_color.vsh +++ /dev/null @@ -1,23 +0,0 @@ -/* - psp_color vertex shader - - Original code written by hunterk, modified by Pokefan531 and - released into the public domain - - 'Ported' (i.e. copy/paste) to PPSSPP format by jdgleaver - - 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; either version 2 of the License, or (at your option) - any later version. -*/ - -attribute vec4 a_position; -attribute vec2 a_texcoord0; -varying vec2 v_texcoord0; - -void main() -{ - v_texcoord0 = a_texcoord0; // + 0.000001; // HLSL precision workaround (is this needed???) - gl_Position = a_position; -} diff --git a/ext/libkirk/ec.c b/ext/libkirk/ec.c index 89892ddf0f..bfdd86bcc4 100644 --- a/ext/libkirk/ec.c +++ b/ext/libkirk/ec.c @@ -295,8 +295,8 @@ static void generate_ecdsa(u8 *outR, u8 *outS, u8 *k, u8 *hash) bn_from_mon(R, ec_N, 21); bn_from_mon(S, ec_N, 21); - memcpy(outR,R+1,0x20); - memcpy(outS,S+1,0x20); + memcpy(outR,R+1,20); + memcpy(outS,S+1,20); } // Signing = diff --git a/ext/native/tools/atlastool.cpp b/ext/native/tools/atlastool.cpp index 53bda41e86..29c866509a 100644 --- a/ext/native/tools/atlastool.cpp +++ b/ext/native/tools/atlastool.cpp @@ -462,8 +462,8 @@ void RasterizeFonts(const FontReferenceList &fontRefs, vector &ranges } else { dist = -closest.find_closest(ctx, cty, 1); } - dist = dist / supersample * distmult + 127.5; - dist = floor(dist + 0.5); + dist = dist / supersample * distmult + 127.5f; + dist = floor(dist + 0.5f); if (dist < 0) dist = 0; if (dist > 255) dist = 255; @@ -564,7 +564,7 @@ struct FontDesc { } } - height = metrics_height / 64.0 / supersample; + height = metrics_height / 64.0f / supersample; } void OutputSelf(FILE *fil, float tw, float th, const vector &results) const { @@ -987,7 +987,7 @@ int main(int argc, char **argv) { fwrite(&header, 1, sizeof(header), meta); // For each image for (int i = 0; i < (int)images.size(); i++) { - AtlasImage atlas_image = images[i].ToAtlasImage(dest.width(), dest.height(), results); + AtlasImage atlas_image = images[i].ToAtlasImage((float)dest.width(), (float)dest.height(), results); fwrite(&atlas_image, 1, sizeof(atlas_image), meta); } // For each font @@ -998,7 +998,7 @@ int main(int argc, char **argv) { fwrite(&font_header, 1, sizeof(font_header), meta); auto ranges = font.GetRanges(); fwrite(ranges.data(), sizeof(AtlasCharRange), ranges.size(), meta); - auto chars = font.GetChars(dest.width(), dest.height(), results); + auto chars = font.GetChars((float)dest.width(), (float)dest.height(), results); fwrite(chars.data(), sizeof(AtlasChar), chars.size(), meta); } fclose(meta); @@ -1010,7 +1010,7 @@ int main(int argc, char **argv) { for (int i = 0; i < (int)fonts.size(); i++) { FontDesc &xfont = fonts[i]; xfont.ComputeHeight(results, distmult); - xfont.OutputSelf(cpp_file, dest.width(), dest.height(), results); + xfont.OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results); } if (fonts.size()) { @@ -1024,7 +1024,7 @@ int main(int argc, char **argv) { if (images.size()) { fprintf(cpp_file, "const AtlasImage %s_images[%i] = {\n", atlas_name, (int)images.size()); for (int i = 0; i < (int)images.size(); i++) { - images[i].OutputSelf(cpp_file, dest.width(), dest.height(), results); + images[i].OutputSelf(cpp_file, (float)dest.width(), (float)dest.height(), results); } fprintf(cpp_file, "};\n"); } diff --git a/ext/native/tools/atlastool/atlastool.vcxproj b/ext/native/tools/atlastool/atlastool.vcxproj index afd5ad8b43..dbbfbfefee 100644 --- a/ext/native/tools/atlastool/atlastool.vcxproj +++ b/ext/native/tools/atlastool/atlastool.vcxproj @@ -70,7 +70,7 @@ Level3 Disabled true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreadedDebug @@ -90,7 +90,7 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true ../../../../;../../ext;../../;../../../../ext;../prebuilt MultiThreaded @@ -108,4 +108,4 @@ - + \ No newline at end of file diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 20b845ba2e..d30aac7962 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -358,6 +358,7 @@ int main(int argc, const char* argv[]) coreParameter.startBreak = false; coreParameter.printfEmuLog = !autoCompare; coreParameter.headLess = true; + coreParameter.renderScaleFactor = 1; coreParameter.renderWidth = 480; coreParameter.renderHeight = 272; coreParameter.pixelWidth = 480; @@ -394,9 +395,12 @@ int main(int argc, const char* argv[]) g_Config.iSplineBezierQuality = 2; g_Config.bHighQualityDepth = true; g_Config.bMemStickInserted = true; + g_Config.iMemStickSizeGB = 16; g_Config.bFragmentTestCache = true; g_Config.bEnableWlan = true; g_Config.sMACAddress = "12:34:56:78:9A:BC"; + g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE; + g_Config.iPSPModel = PSP_MODEL_SLIM; #ifdef _WIN32 g_Config.internalDataDirectory = ""; diff --git a/headless/StubHost.h b/headless/StubHost.h index 8ac54ddff8..571444ce0b 100644 --- a/headless/StubHost.h +++ b/headless/StubHost.h @@ -72,6 +72,8 @@ public: void SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) override; + void NotifySwitchUMDUpdated() override {} + // Unique for HeadlessHost virtual void SwapBuffers() {} diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 02b18a5ec5..1f665e4b37 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -34,6 +34,7 @@ #include "Core/System.h" #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbGps.h" +#include "Core/Host.h" #include #include @@ -202,6 +203,10 @@ static LocationHelper *locationHelper; graphicsContext = new IOSGraphicsContext(); + graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) { + host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback"); + }, nullptr); + graphicsContext->ThreadStart(); dp_xscale = (float)dp_xres / (float)pixel_xres; diff --git a/libretro/LibretroD3D11Context.cpp b/libretro/LibretroD3D11Context.cpp index 216876a409..bcb71996b9 100644 --- a/libretro/LibretroD3D11Context.cpp +++ b/libretro/LibretroD3D11Context.cpp @@ -31,6 +31,12 @@ void LibretroD3D11Context::CreateDrawContext() { return; } + // Reject lower feature levels. We have D3D9 for these ancient GPUs. + if (d3d11_->featureLevel < D3D_FEATURE_LEVEL_10_0) { + ERROR_LOG(G3D, "D3D11 featureLevel not high enough - rejecting!\n"); + return; + } + ptr_D3DCompile = d3d11_->D3DCompile; ID3D11Device1 *device1 = nullptr; diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index 5d62a54b45..2b9e944360 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -31,16 +31,15 @@ static bool create_device(retro_vulkan_context *context, VkInstance instance, Vk init_glslang(); if (!VulkanLoad()) { - // TODO: In the context of RetroArch, someone has already loaded the functions - - // we shouldn't need to load them again. On the other hand, it can't really hurt, we're gonna - // get the same pointers. + // TODO: In the context of RetroArch, someone has already loaded the functions. + // But grabbing the pointers for ourselves can't really be a bad thing. ERROR_LOG(G3D, "RetroArch called the Vulkan entry point without Vulkan available???"); return false; } vk = new VulkanContext(); - vk_libretro_init(instance, gpu, surface, get_instance_proc_addr, required_device_extensions, num_required_device_extensions, required_device_layers, num_required_device_layers, required_features); + vk_libretro_init(instance, gpu, surface, get_instance_proc_addr, required_device_extensions, num_required_device_extensions, required_device_layers, num_required_device_layers, required_features); // TODO: Here we'll inject the instance and all of the stuff into the VulkanContext. @@ -69,10 +68,6 @@ static bool create_device(retro_vulkan_context *context, VkInstance instance, Vk vk->InitSurface(WINDOWSYSTEM_WAYLAND, nullptr, nullptr); #endif - if (!vk->InitSwapchain()) { - return false; - } - context->gpu = vk->GetPhysicalDevice(physical_device); context->device = vk->GetDevice(); context->queue = vk->GetGraphicsQueue(); @@ -100,13 +95,44 @@ bool LibretroVulkanContext::Init() { return false; } - static const struct retro_hw_render_context_negotiation_interface_vulkan iface = { RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, GetApplicationInfo, create_device, nullptr }; + static const struct retro_hw_render_context_negotiation_interface_vulkan iface = { + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, + RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, + GetApplicationInfo, + create_device, // Callback above. + nullptr, + }; Libretro::environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void *)&iface); g_Config.iGPUBackend = (int)GPUBackend::VULKAN; return true; } +void LibretroVulkanContext::ContextReset() { + retro_hw_render_interface *vulkan; + if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&vulkan) || !vulkan) { + ERROR_LOG(G3D, "Failed to get HW rendering interface!\n"); + return; + } + if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) { + ERROR_LOG(G3D, "HW render interface mismatch, expected %u, got %u!\n", RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, vulkan->interface_version); + return; + } + vk_libretro_set_hwrender_interface(vulkan); + + LibretroHWRenderContext::ContextReset(); +} + +void LibretroVulkanContext::CreateDrawContext() { + vk->ReinitSurface(); + + if (!vk->InitSwapchain()) { + return; + } + + draw_ = Draw::T3DCreateVulkanContext(vk, false); +} + void LibretroVulkanContext::Shutdown() { LibretroHWRenderContext::Shutdown(); @@ -128,24 +154,3 @@ void LibretroVulkanContext::Shutdown() { } void *LibretroVulkanContext::GetAPIContext() { return vk; } - -void LibretroVulkanContext::CreateDrawContext() { - retro_hw_render_interface *vulkan; - if (!Libretro::environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&vulkan) || !vulkan) { - ERROR_LOG(G3D, "Failed to get HW rendering interface!\n"); - return; - } - if (vulkan->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) { - ERROR_LOG(G3D, "HW render interface mismatch, expected %u, got %u!\n", RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, vulkan->interface_version); - return; - } - vk_libretro_set_hwrender_interface(vulkan); - - vk->ReinitSurface(); - - if (!vk->InitSwapchain()) { - return; - } - - draw_ = Draw::T3DCreateVulkanContext(vk, false); -} diff --git a/libretro/LibretroVulkanContext.h b/libretro/LibretroVulkanContext.h index e7386a43d0..1da32aec6c 100644 --- a/libretro/LibretroVulkanContext.h +++ b/libretro/LibretroVulkanContext.h @@ -12,6 +12,9 @@ public: void *GetAPIContext() override; void CreateDrawContext() override; + + void ContextReset() override; + GPUCore GetGPUCore() override { return GPUCORE_VULKAN; } const char *Ident() override { return "Vulkan"; } }; diff --git a/libretro/Makefile b/libretro/Makefile index 8b2d15fa49..a047d9a2e8 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -284,6 +284,10 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) LDFLAGS += -APPCONTAINER -NXCOMPAT -DYNAMICBASE -MANIFEST:NO -OPT:REF -SUBSYSTEM:CONSOLE -MANIFESTUAC:NO -OPT:ICF -ERRORREPORT:PROMPT -NOLOGO -TLBID:1 -DEBUG:FULL -WINMD:NO LIBS += WindowsApp.lib endif + + ifeq ($(DEBUG), 1) + MSVC2017CompileFlags += -DEBUG + endif CFLAGS += $(MSVC2017CompileFlags) -nologo CXXFLAGS += $(MSVC2017CompileFlags) -nologo -EHsc @@ -392,6 +396,10 @@ else ifneq (,$(findstring windows_msvc2019,$(platform))) LIBS += WindowsApp.lib endif + ifeq ($(DEBUG), 1) + MSVC2019CompileFlags += -DEBUG + endif + CFLAGS += $(MSVC2019CompileFlags) -nologo CXXFLAGS += $(MSVC2019CompileFlags) -nologo -EHsc diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 950b6a864a..c14fde2553 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -166,6 +166,9 @@ SOURCES_CXX += \ $(COMMONDIR)/File/FileDescriptor.cpp \ $(COMMONDIR)/File/DirListing.cpp \ $(COMMONDIR)/GPU/thin3d.cpp \ + $(COMMONDIR)/GPU/Shader.cpp \ + $(COMMONDIR)/GPU/ShaderWriter.cpp \ + $(COMMONDIR)/GPU/ShaderTranslation.cpp \ $(COMMONDIR)/GPU/OpenGL/thin3d_gl.cpp \ $(COMMONDIR)/GPU/OpenGL/GLDebugLog.cpp \ $(COMMONDIR)/GPU/OpenGL/GLSLProgram.cpp \ @@ -232,10 +235,10 @@ SOURCES_CXX += \ $(GPUCOMMONDIR)/SplineCommon.cpp \ $(GPUCOMMONDIR)/FramebufferManagerCommon.cpp \ $(GPUCOMMONDIR)/PresentationCommon.cpp \ + $(GPUCOMMONDIR)/ReinterpretFramebuffer.cpp \ $(GPUCOMMONDIR)/ShaderId.cpp \ $(GPUCOMMONDIR)/ShaderCommon.cpp \ $(GPUCOMMONDIR)/ShaderUniforms.cpp \ - $(GPUCOMMONDIR)/ShaderTranslation.cpp \ $(GPUCOMMONDIR)/GPUDebugInterface.cpp \ $(GPUCOMMONDIR)/DepalettizeShaderCommon.cpp \ $(GPUCOMMONDIR)/TransformCommon.cpp \ @@ -248,6 +251,8 @@ SOURCES_CXX += \ $(GPUDIR)/Debugger/Playback.cpp \ $(GPUDIR)/Debugger/Record.cpp \ $(GPUDIR)/Debugger/Stepping.cpp \ + $(GPUDIR)/Common/FragmentShaderGenerator.cpp \ + $(GPUDIR)/Common/VertexShaderGenerator.cpp \ $(GPUDIR)/Common/TextureCacheCommon.cpp \ $(GPUDIR)/Common/TextureScalerCommon.cpp \ $(GPUDIR)/Common/SoftwareTransformCommon.cpp \ @@ -268,10 +273,8 @@ SOURCES_CXX += \ $(GPUDIR)/Software/RasterizerRectangle.cpp \ $(GPUDIR)/GLES/DepalettizeShaderGLES.cpp \ $(GPUDIR)/GLES/DepthBufferGLES.cpp \ - $(GPUDIR)/GLES/VertexShaderGeneratorGLES.cpp \ $(GPUDIR)/GLES/DrawEngineGLES.cpp \ $(GPUDIR)/GLES/GPU_GLES.cpp \ - $(GPUDIR)/GLES/FragmentShaderGeneratorGLES.cpp \ $(GPUDIR)/GLES/FragmentTestCacheGLES.cpp \ $(GPUDIR)/GLES/FramebufferManagerGLES.cpp \ $(GPUDIR)/GLES/TextureCacheGLES.cpp \ @@ -526,7 +529,7 @@ ifeq ($(WITH_DYNAREC),1) $(COREDIR)/MIPS/ARM/ArmJit.cpp \ $(COREDIR)/MIPS/ARM/ArmRegCache.cpp \ $(COREDIR)/MIPS/ARM/ArmRegCacheFPU.cpp \ - $(EXTDIR)/disarm.cpp \ + $(EXTDIR)/disarm.cpp \ $(GPUCOMMONDIR)/VertexDecoderArm.cpp ifeq ($(HAVE_NEON),1) @@ -569,7 +572,7 @@ ifeq ($(WITH_DYNAREC),1) SOURCES_C += $(EXTDIR)/libpng17/arm/arm_init.c \ $(EXTDIR)/libpng17/arm/filter_neon_intrinsics.c - ASMFILES += $(NATIVEDIR)/math/fast/fast_matrix_neon.S \ + ASMFILES += $(COMMONDIR)/math/fast/fast_matrix_neon.S \ $(EXTDIR)/libpng17/arm/filter_neon.S endif else @@ -639,7 +642,6 @@ SOURCES_C += \ SOURCES_CXX += \ $(GPUDIR)/Vulkan/DepalettizeShaderVulkan.cpp \ $(GPUDIR)/Vulkan/DrawEngineVulkan.cpp \ - $(GPUDIR)/Vulkan/FragmentShaderGeneratorVulkan.cpp \ $(GPUDIR)/Vulkan/FramebufferManagerVulkan.cpp \ $(GPUDIR)/Vulkan/GPU_Vulkan.cpp \ $(GPUDIR)/Vulkan/PipelineManagerVulkan.cpp \ @@ -648,7 +650,6 @@ SOURCES_CXX += \ $(GPUDIR)/Vulkan/StencilBufferVulkan.cpp \ $(GPUDIR)/Vulkan/TextureCacheVulkan.cpp \ $(GPUDIR)/Vulkan/TextureScalerVulkan.cpp \ - $(GPUDIR)/Vulkan/VertexShaderGeneratorVulkan.cpp \ $(GPUDIR)/Vulkan/VulkanUtil.cpp \ $(LIBRETRODIR)/LibretroVulkanContext.cpp \ $(LIBRETRODIR)/libretro_vulkan.cpp @@ -663,20 +664,17 @@ ifeq ($(PLATFORM_EXT), win32) SOURCES_CXX += \ $(GPUDIR)/Directx9/DepalettizeShaderDX9.cpp \ $(GPUDIR)/Directx9/DrawEngineDX9.cpp \ - $(GPUDIR)/Directx9/PixelShaderGeneratorDX9.cpp \ $(GPUDIR)/Directx9/FramebufferManagerDX9.cpp \ $(GPUDIR)/Directx9/GPU_DX9.cpp \ $(GPUDIR)/Directx9/ShaderManagerDX9.cpp \ $(GPUDIR)/Directx9/StateMappingDX9.cpp \ $(GPUDIR)/Directx9/StencilBufferDX9.cpp \ $(GPUDIR)/Directx9/TextureCacheDX9.cpp \ - $(GPUDIR)/Directx9/TextureScalerDX9.cpp \ - $(GPUDIR)/Directx9/VertexShaderGeneratorDX9.cpp + $(GPUDIR)/Directx9/TextureScalerDX9.cpp SOURCES_CXX += \ $(GPUDIR)/D3D11/DepalettizeShaderD3D11.cpp \ $(GPUDIR)/D3D11/DrawEngineD3D11.cpp \ - $(GPUDIR)/D3D11/FragmentShaderGeneratorD3D11.cpp \ $(GPUDIR)/D3D11/FramebufferManagerD3D11.cpp \ $(GPUDIR)/D3D11/GPU_D3D11.cpp \ $(GPUDIR)/D3D11/D3D11Util.cpp \ @@ -685,7 +683,6 @@ SOURCES_CXX += \ $(GPUDIR)/D3D11/StencilBufferD3D11.cpp \ $(GPUDIR)/D3D11/TextureCacheD3D11.cpp \ $(GPUDIR)/D3D11/TextureScalerD3D11.cpp \ - $(GPUDIR)/D3D11/VertexShaderGeneratorD3D11.cpp \ $(LIBRETRODIR)/LibretroD3D11Context.cpp SOURCES_CXX += \ diff --git a/libretro/README_WINDOWS.txt b/libretro/README_WINDOWS.txt new file mode 100644 index 0000000000..755658dca4 --- /dev/null +++ b/libretro/README_WINDOWS.txt @@ -0,0 +1,37 @@ +Install msys. + +Follow the guide here to set up your MSYS installation for retroarch compilation: + +https://docs.libretro.com/development/retroarch/compilation/windows/ + +You can probably really skip most of the packages but you need make: + +pacman -S make + +"Install" the plugin in Retroarch. + +Then use the following in msys: + +cd libretro +make platform=windows_msvc2019_desktop_x64 -j32 && cp ppsspp_libretro.* /d/retroarch/cores && rm nul + +Note that the latter part copies the DLL/PDB into wherever retroarch reads it from. Might need to adjust the path, +and adjust -j32 depending on your number of logical CPUs - might not need that many threads (or you might need more...). + +(plain make without a platform parameter doesn't work - g++ isn't able to build the D3D11 stuff, or at least it fails to link). + +To debug from within MSVC, open retroarch.exe (or retroarch_debug.exe) as a Project/Solution, then open a few of the cpp files, +set some breakpoints and just launch using F5. + +If you get + +``` +../git-version.cpp(3): error C2374: 'PPSSPP_GIT_VERSION': redefinition; multiple initialization +../git-version.cpp(1): note: see declaration of 'PPSSPP_GIT_VERSION' +``` +just do `rm ../git-version.cpp`. + + +Useful libretro/vulkan sample code: + +https://github.com/libretro/libretro-samples/blob/master/video/vulkan/vk_rendering/libretro-test.c diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index bdb5f8d1b1..68f8c0ec7d 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -34,6 +34,7 @@ #include "GPU/GPUInterface.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/TextureScalerCommon.h" +#include "GPU/Common/PresentationCommon.h" #include "libretro/libretro.h" #include "libretro/LibretroGraphicsContext.h" @@ -208,6 +209,7 @@ static RetroOption ppsspp_texture_scaling_level("ppsspp_texture_scaling_lev static RetroOption ppsspp_texture_scaling_type("ppsspp_texture_scaling_type", "Texture Scaling Type", { { "xbrz", TextureScalerCommon::XBRZ }, { "hybrid", TextureScalerCommon::HYBRID }, { "bicubic", TextureScalerCommon::BICUBIC }, { "hybrid_bicubic", TextureScalerCommon::HYBRID_BICUBIC } }); static RetroOption ppsspp_texture_filtering("ppsspp_texture_filtering", "Texture Filtering", { { "auto", 1 }, { "nearest", 2 }, { "linear", 3 }, { "linear(FMV)", 4 } }); static RetroOption ppsspp_texture_anisotropic_filtering("ppsspp_texture_anisotropic_filtering", "Anisotropic Filtering", { "off", "1x", "2x", "4x", "8x", "16x" }); +static RetroOption ppsspp_lower_resolution_for_effects("ppsspp_lower_resolution_for_effects", "Lower resolution for effects", { "off", "safe", "balanced", "aggressive" }); static RetroOption ppsspp_texture_deposterize("ppsspp_texture_deposterize", "Texture Deposterize", false); static RetroOption ppsspp_texture_replacement("ppsspp_texture_replacement", "Texture Replacement", false); static RetroOption ppsspp_gpu_hardware_transform("ppsspp_gpu_hardware_transform", "GPU Hardware T&L", true); @@ -241,6 +243,7 @@ void retro_set_environment(retro_environment_t cb) vars.push_back(ppsspp_unsafe_func_replacements.GetOptions()); vars.push_back(ppsspp_cheats.GetOptions()); vars.push_back(ppsspp_io_timing_method.GetOptions()); + vars.push_back(ppsspp_lower_resolution_for_effects.GetOptions()); vars.push_back({}); environ_cb = cb; @@ -311,6 +314,7 @@ static void check_variables(CoreParameter &coreParam) ppsspp_rendering_mode.Update(&g_Config.iRenderingMode); ppsspp_cpu_core.Update((CPUCore *)&g_Config.iCpuCore); ppsspp_io_timing_method.Update((IOTimingMethods *)&g_Config.iIOTimingMethod); + ppsspp_lower_resolution_for_effects.Update(&g_Config.iBloomHack); ppsspp_language.Update(&g_Config.iLanguage); if (g_Config.iLanguage < 0) @@ -318,10 +322,8 @@ static void check_variables(CoreParameter &coreParam) if (!PSP_IsInited() && ppsspp_internal_resolution.Update(&g_Config.iInternalResolution)) { - coreParam.pixelWidth = coreParam.renderWidth = - g_Config.iInternalResolution * 480; - coreParam.pixelHeight = coreParam.renderHeight = - g_Config.iInternalResolution * 272; + coreParam.pixelWidth = coreParam.renderWidth = g_Config.iInternalResolution * 480; + coreParam.pixelHeight = coreParam.renderHeight = g_Config.iInternalResolution * 272; if (gpu) { @@ -353,11 +355,12 @@ void retro_init(void) g_Config.bEnableLogging = true; g_Config.iUnthrottleMode = (int)UnthrottleMode::CONTINUOUS; - g_Config.bMemStickInserted = PSP_MEMORYSTICK_STATE_INSERTED; + g_Config.bMemStickInserted = true; g_Config.iGlobalVolume = VOLUME_MAX - 1; g_Config.iAltSpeedVolume = -1; g_Config.bEnableSound = true; g_Config.iCwCheatRefreshRate = 60; + g_Config.iMemStickSizeGB = 16; g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE; g_Config.iPSPModel = PSP_MODEL_SLIM; @@ -421,7 +424,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info) info->geometry.base_height = g_Config.iInternalResolution * 272; info->geometry.max_width = g_Config.iInternalResolution * 480; info->geometry.max_height = g_Config.iInternalResolution * 272; - info->geometry.aspect_ratio = 16.0 / 9.0; + info->geometry.aspect_ratio = 480.0 / 272.0; // Not 16:9! But very, very close. } unsigned retro_api_version(void) { return RETRO_API_VERSION; } @@ -889,6 +892,11 @@ float System_GetPropertyFloat(SystemProperty prop) { case SYSPROP_DISPLAY_REFRESH_RATE: return 60.f; + case SYSPROP_DISPLAY_SAFE_INSET_LEFT: + case SYSPROP_DISPLAY_SAFE_INSET_RIGHT: + case SYSPROP_DISPLAY_SAFE_INSET_TOP: + case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM: + return 0.0f; default: break; } @@ -904,6 +912,8 @@ void NativeResized() {} #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) std::vector __cameraGetDeviceList() { return std::vector(); } +bool audioRecording_Available() { return false; } +bool audioRecording_State() { return false; } void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function cb) { cb(false, ""); } #endif diff --git a/libretro/libretro_vulkan.cpp b/libretro/libretro_vulkan.cpp index 09e08703fe..1081f95e0b 100644 --- a/libretro/libretro_vulkan.cpp +++ b/libretro/libretro_vulkan.cpp @@ -1,3 +1,10 @@ +// Debugging notes +// The crash happens when we try to call vkGetPhysicalDeviceProperties2KHR which seems to be null. +// +// Apparently we don't manage to specify the extensions we want. Still something reports that this one +// is present? +// Failed to load : vkGetPhysicalDeviceProperties2KHR +// Failed to load : vkGetPhysicalDeviceFeatures2KHR #include #include @@ -5,6 +12,9 @@ #include #include +#include "Common/Log.h" +#include "Core/Config.h" + #define VK_NO_PROTOTYPES #include "libretro/libretro_vulkan.h" @@ -48,24 +58,25 @@ struct VkSwapchainKHR_T { }; static VkSwapchainKHR_T chain; -#define LIBRETRO_VK_WARP_LIST() \ - LIBRETRO_VK_WARP_FUNC(vkDestroyInstance); \ - LIBRETRO_VK_WARP_FUNC(vkCreateDevice); \ - LIBRETRO_VK_WARP_FUNC(vkDestroyDevice); \ - LIBRETRO_VK_WARP_FUNC(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); \ - LIBRETRO_VK_WARP_FUNC(vkDestroySurfaceKHR); \ - LIBRETRO_VK_WARP_FUNC(vkCreateSwapchainKHR); \ - LIBRETRO_VK_WARP_FUNC(vkGetSwapchainImagesKHR); \ - LIBRETRO_VK_WARP_FUNC(vkAcquireNextImageKHR); \ - LIBRETRO_VK_WARP_FUNC(vkQueuePresentKHR); \ - LIBRETRO_VK_WARP_FUNC(vkDestroySwapchainKHR); \ - LIBRETRO_VK_WARP_FUNC(vkQueueSubmit); \ - LIBRETRO_VK_WARP_FUNC(vkQueueWaitIdle); \ - LIBRETRO_VK_WARP_FUNC(vkCmdPipelineBarrier); \ - LIBRETRO_VK_WARP_FUNC(vkCreateRenderPass) +#define LIBRETRO_VK_WARP_LIST() \ + LIBRETRO_VK_WARP_FUNC(vkCreateInstance); \ + LIBRETRO_VK_WARP_FUNC(vkDestroyInstance); \ + LIBRETRO_VK_WARP_FUNC(vkCreateDevice); \ + LIBRETRO_VK_WARP_FUNC(vkDestroyDevice); \ + LIBRETRO_VK_WARP_FUNC(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); \ + LIBRETRO_VK_WARP_FUNC(vkDestroySurfaceKHR); \ + LIBRETRO_VK_WARP_FUNC(vkCreateSwapchainKHR); \ + LIBRETRO_VK_WARP_FUNC(vkGetSwapchainImagesKHR); \ + LIBRETRO_VK_WARP_FUNC(vkAcquireNextImageKHR); \ + LIBRETRO_VK_WARP_FUNC(vkQueuePresentKHR); \ + LIBRETRO_VK_WARP_FUNC(vkDestroySwapchainKHR); \ + LIBRETRO_VK_WARP_FUNC(vkQueueSubmit); \ + LIBRETRO_VK_WARP_FUNC(vkQueueWaitIdle); \ + LIBRETRO_VK_WARP_FUNC(vkCmdPipelineBarrier); \ + LIBRETRO_VK_WARP_FUNC(vkCreateRenderPass); -#define LIBRETRO_VK_WARP_FUNC(x) \ - extern PFN_##x x; \ +#define LIBRETRO_VK_WARP_FUNC(x) \ + extern PFN_##x x; \ PFN_##x x##_org LIBRETRO_VK_WARP_FUNC(vkGetInstanceProcAddr); @@ -124,8 +135,15 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkCreateLibretroSurfaceKHR(VkInstance inst VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR_libretro(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) { VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR_org(physicalDevice, surface, pSurfaceCapabilities); if (res == VK_SUCCESS) { - pSurfaceCapabilities->currentExtent.width = -1; - pSurfaceCapabilities->currentExtent.height = -1; + int w = g_Config.iInternalResolution * 480; + int h = g_Config.iInternalResolution * 272; + + pSurfaceCapabilities->minImageExtent.width = w; + pSurfaceCapabilities->minImageExtent.height = h; + pSurfaceCapabilities->maxImageExtent.width = w; + pSurfaceCapabilities->maxImageExtent.height = h; + pSurfaceCapabilities->currentExtent.width = w; + pSurfaceCapabilities->currentExtent.height = h; } return res; } @@ -332,13 +350,11 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass_libretro(VkDevice device, cons } #undef LIBRETRO_VK_WARP_FUNC -#define LIBRETRO_VK_WARP_FUNC(x) \ - do { \ - if (!strcmp(pName, #x)) { \ - x##_org = (PFN_##x)fptr; \ - return (PFN_vkVoidFunction)x##_libretro; \ - } \ - } while (0) +#define LIBRETRO_VK_WARP_FUNC(x) \ + if (!strcmp(pName, #x)) { \ + x##_org = (PFN_##x)fptr; \ + return (PFN_vkVoidFunction)x##_libretro; \ + } VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr_libretro(VkInstance instance, const char *pName) { if (false @@ -362,8 +378,10 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr_libretro(VkInstan } PFN_vkVoidFunction fptr = vkGetInstanceProcAddr_org(instance, pName); - if (!fptr) - return fptr; + if (!fptr) { + ERROR_LOG(G3D, "Failed to load VK instance function: %s", pName); + return fptr; + } LIBRETRO_VK_WARP_LIST(); @@ -400,9 +418,12 @@ void vk_libretro_init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR su vkCreateInstance = vkCreateInstance_libretro; } -void vk_libretro_set_hwrender_interface(retro_hw_render_interface *hw_render_interface) { vulkan = (retro_hw_render_interface_vulkan *)hw_render_interface; } +void vk_libretro_set_hwrender_interface(retro_hw_render_interface *hw_render_interface) { + vulkan = (retro_hw_render_interface_vulkan *)hw_render_interface; +} + void vk_libretro_shutdown() { - memset(&vk_init_info, 0x00, sizeof(vk_init_info)); - vulkan = NULL; + memset(&vk_init_info, 0, sizeof(vk_init_info)); + vulkan = nullptr; DEDICATED_ALLOCATION = false; } diff --git a/pspautotests b/pspautotests index 328b839c72..879e7959eb 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 328b839c7243e7f733f9eae88d059485e3d808e7 +Subproject commit 879e7959eb3251133fcc4ccf26b0ef35d9d43f8e diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp new file mode 100644 index 0000000000..88fa67f439 --- /dev/null +++ b/unittest/TestShaderGenerators.cpp @@ -0,0 +1,368 @@ +#include + +#include "Common/StringUtils.h" + +#include "GPU/Common/ShaderId.h" +#include "GPU/Common/ShaderCommon.h" +#include "GPU/Common/GPUStateUtils.h" +#include "Common/Data/Random/Rng.h" + +#include "GPU/Vulkan/VulkanContext.h" + +#include "GPU/Common/FragmentShaderGenerator.h" +#include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/ReinterpretFramebuffer.h" + +#include "GPU/D3D11/D3D11Util.h" +#include "GPU/D3D11/D3D11Loader.h" + +#include "GPU/D3D9/D3DCompilerLoader.h" +#include "GPU/D3D9/D3D9ShaderCompiler.h" + +bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, std::string *errorString) { + uint64_t uniformMask; + switch (lang) { + case ShaderLanguage::GLSL_VULKAN: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN); + return GenerateFragmentShader(id, buffer, compat, &uniformMask, errorString); + } + case ShaderLanguage::GLSL_1xx: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + return GenerateFragmentShader(id, buffer, compat, &uniformMask, errorString); + } + case ShaderLanguage::GLSL_3xx: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + return GenerateFragmentShader(id, buffer, compat, &uniformMask, errorString); + } + case ShaderLanguage::HLSL_D3D9: + { + ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D9); + return GenerateFragmentShader(id, buffer, compat, &uniformMask, errorString); + } + case ShaderLanguage::HLSL_D3D11: + { + ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11); + return GenerateFragmentShader(id, buffer, compat, &uniformMask, errorString); + } + default: + return false; + } +} + +bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, std::string *errorString) { + uint32_t attrMask; + uint64_t uniformMask; + switch (lang) { + case ShaderLanguage::GLSL_VULKAN: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN); + return GenerateVertexShader(id, buffer, compat, &attrMask, &uniformMask, errorString); + } + case ShaderLanguage::GLSL_1xx: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + return GenerateVertexShader(id, buffer, compat, &attrMask, &uniformMask, errorString); + } + case ShaderLanguage::GLSL_3xx: + { + ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx); + return GenerateVertexShader(id, buffer, compat, &attrMask, &uniformMask, errorString); + } + case ShaderLanguage::HLSL_D3D9: + { + ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D9); + return GenerateVertexShader(id, buffer, compat, &attrMask, &uniformMask, errorString); + } + case ShaderLanguage::HLSL_D3D11: + { + ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11); + return GenerateVertexShader(id, buffer, compat, &attrMask, &uniformMask, errorString); + } + default: + return false; + } +} + +bool TestCompileShader(const char *buffer, ShaderLanguage lang, bool vertex, std::string *errorMessage) { + std::vector spirv; + switch (lang) { + case ShaderLanguage::HLSL_D3D11: + { + auto output = CompileShaderToBytecodeD3D11(buffer, strlen(buffer), vertex ? "vs_4_0" : "ps_4_0", 0); + return !output.empty(); + } + case ShaderLanguage::HLSL_D3D9: + { + LPD3DBLOB blob = CompileShaderToByteCodeD3D9(buffer, vertex ? "vs_2_0" : "ps_2_0", errorMessage); + if (blob) { + blob->Release(); + return true; + } else { + return false; + } + } + + case ShaderLanguage::GLSL_VULKAN: + return GLSLtoSPV(vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::VULKAN, spirv, errorMessage); + case ShaderLanguage::GLSL_1xx: + return GLSLtoSPV(vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::GL140, spirv, errorMessage); + case ShaderLanguage::GLSL_3xx: + return GLSLtoSPV(vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::GLES300, spirv, errorMessage); + default: + return false; + } +} + +void PrintDiff(const char *a, const char *b) { + // Stupidest diff ever: Just print both lines, and a few around it, when we find a mismatch. + std::vector a_lines; + std::vector b_lines; + SplitString(a, '\n', a_lines); + SplitString(b, '\n', b_lines); + for (size_t i = 0; i < a_lines.size() && i < b_lines.size(); i++) { + if (a_lines[i] != b_lines[i]) { + // Print some context + for (size_t j = std::max((int)i - 4, 0); j < i; j++) { + printf("%s\n", a_lines[j].c_str()); + } + printf("DIFF found at line %d:\n", (int)i); + printf("a: %s\n", a_lines[i].c_str()); + printf("b: %s\n", b_lines[i].c_str()); + printf("...continues...\n"); + for (size_t j = i + 1; j < i + 5 && j < a_lines.size() && j < b_lines.size(); j++) { + printf("a: %s\n", a_lines[j].c_str()); + printf("b: %s\n", b_lines[j].c_str()); + } + printf("==================\n"); + return; + } + } +} + +const char *ShaderLanguageToString(ShaderLanguage lang) { + switch (lang) { + case HLSL_D3D11: return "HLSL_D3D11"; + case HLSL_D3D9: return "HLSL_D3D9"; + case GLSL_VULKAN: return "GLSL_VULKAN"; + case GLSL_1xx: return "GLSL_1xx"; + case GLSL_3xx: return "GLSL_3xx"; + default: return "N/A"; + } +} + +bool TestReinterpretShaders() { + ShaderLanguage languages[] = { + ShaderLanguage::HLSL_D3D11, + ShaderLanguage::GLSL_VULKAN, + ShaderLanguage::GLSL_3xx, + }; + GEBufferFormat fmts[3] = { + GE_FORMAT_565, + GE_FORMAT_5551, + GE_FORMAT_4444, + }; + char *buffer = new char[65536]; + + // Generate all despite failures - it's only 6. + bool failed = false; + + for (int k = 0; k < ARRAY_SIZE(languages); k++) { + ShaderLanguageDesc desc(languages[k]); + if (!GenerateReinterpretVertexShader(buffer, desc)) { + printf("Failed!\n%s\n", buffer); + failed = true; + } else { + std::string errorMessage; + if (!TestCompileShader(buffer, languages[k], true, &errorMessage)) { + printf("Error compiling fragment shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str()); + failed = true; + return false; + } else { + //printf("===\n%s\n===\n", buffer); + } + } + } + + for (int k = 0; k < ARRAY_SIZE(languages); k++) { + printf("=== %s ===\n\n", ShaderLanguageToString(languages[k])); + + ShaderLanguageDesc desc(languages[k]); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == j) + continue; // useless shader! + if (!GenerateReinterpretFragmentShader(buffer, fmts[i], fmts[j], desc)) { + printf("Failed!\n%s\n", buffer); + failed = true; + } else { + std::string errorMessage; + if (!TestCompileShader(buffer, languages[k], false, &errorMessage)) { + printf("Error compiling fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str()); + failed = true; + return false; + } else { + printf("===\n%s\n===\n", buffer); + } + } + } + } + + } + return !failed; +} + +const ShaderLanguage languages[] = { + ShaderLanguage::HLSL_D3D9, + ShaderLanguage::HLSL_D3D11, + ShaderLanguage::GLSL_VULKAN, + ShaderLanguage::GLSL_1xx, + ShaderLanguage::GLSL_3xx, +}; +const int numLanguages = ARRAY_SIZE(languages); + +bool TestVertexShaders() { + char *buffer[numLanguages]; + + for (int i = 0; i < numLanguages; i++) { + buffer[i] = new char[65536]; + } + GMRng rng; + int successes = 0; + int count = 700; + + // Generate a bunch of random vertex shader IDs, try to generate shader source. + // Then compile it and check that it's ok. + for (int i = 0; i < count; i++) { + uint32_t bottom = rng.R32(); + uint32_t top = rng.R32(); + VShaderID id; + id.d[0] = bottom; + id.d[1] = top; + + // The generated bits need some adjustment: + + // We don't use these bits in the HLSL shader generator. + id.SetBits(VS_BIT_WEIGHT_FMTSCALE, 2, 0); + // If mode is through, we won't do hardware transform. + if (id.Bit(VS_BIT_IS_THROUGH)) { + id.SetBit(VS_BIT_USE_HW_TRANSFORM, 0); + } + if (!id.Bit(VS_BIT_USE_HW_TRANSFORM)) { + id.SetBit(VS_BIT_ENABLE_BONES, 0); + } + + bool generateSuccess[numLanguages]{}; + std::string genErrorString[numLanguages]; + + for (int j = 0; j < numLanguages; j++) { + generateSuccess[j] = GenerateVShader(id, buffer[j], languages[j], &genErrorString[j]); + if (!genErrorString[j].empty()) { + printf("%s\n", genErrorString[j].c_str()); + } + } + + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), + // let's try to compile them. + for (int j = 0; j < numLanguages; j++) { + if (generateSuccess[j]) { + std::string errorMessage; + if (!TestCompileShader(buffer[j], languages[j], true, &errorMessage)) { + printf("Error compiling vertex shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + return false; + } + successes++; + } + } + } + + printf("%d/%d vertex shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages); + + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return true; +} + +bool TestFragmentShaders() { + char *buffer[numLanguages]; + + for (int i = 0; i < numLanguages; i++) { + buffer[i] = new char[65536]; + } + GMRng rng; + int successes = 0; + int count = 300; + + // Generate a bunch of random fragment shader IDs, try to generate shader source. + // Then compile it and check that it's ok. + for (int i = 0; i < count; i++) { + uint32_t bottom = rng.R32(); + uint32_t top = rng.R32(); + FShaderID id; + id.d[0] = bottom; + id.d[1] = top; + + // bits we don't need to test because they are irrelevant on d3d11 + id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, false); + id.SetBit(FS_BIT_SHADER_DEPAL, false); + + // DX9 disabling: + if (static_cast(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)) == ReplaceAlphaType::REPLACE_ALPHA_DUALSOURCE) + continue; + + bool generateSuccess[numLanguages]{}; + std::string genErrorString[numLanguages]; + + for (int j = 0; j < numLanguages; j++) { + generateSuccess[j] = GenerateFShader(id, buffer[j], languages[j], &genErrorString[j]); + if (!genErrorString[j].empty()) { + printf("%s\n", genErrorString[j].c_str()); + } + // We ignore the contents of the error string here, not even gonna try to compile if it errors. + } + + // Now that we have the strings ready for easy comparison (buffer,4 in the watch window), + // let's try to compile them. + for (int j = 0; j < numLanguages; j++) { + if (generateSuccess[j]) { + std::string errorMessage; + if (!TestCompileShader(buffer[j], languages[j], false, &errorMessage)) { + printf("Error compiling fragment shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str()); + return false; + } + successes++; + } + } + } + + printf("%d/%d fragment shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages); + + for (int i = 0; i < numLanguages; i++) { + delete[] buffer[i]; + } + return true; +} + +bool TestShaderGenerators() { + LoadD3D11(); + init_glslang(); + LoadD3DCompilerDynamic(); + + if (!TestFragmentShaders()) { + return false; + } + + if (!TestReinterpretShaders()) { + return false; + } + + if (!TestVertexShaders()) { + return false; + } + + return true; +} diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index a310999360..b5beb3cefa 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -571,6 +571,7 @@ struct TestItem { bool TestArmEmitter(); bool TestArm64Emitter(); bool TestX64Emitter(); +bool TestShaderGenerators(); TestItem availableTests[] = { #if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) @@ -593,7 +594,7 @@ TestItem availableTests[] = { TEST_ITEM(ParseLBN), TEST_ITEM(QuickTexHash), TEST_ITEM(CLZ), - TEST_ITEM(MemMap), + TEST_ITEM(ShaderGenerators), }; int main(int argc, const char *argv[]) { diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index a401a081b7..f585ee5feb 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -382,6 +382,7 @@ true true + @@ -426,4 +427,4 @@ - + \ No newline at end of file diff --git a/unittest/UnitTests.vcxproj.filters b/unittest/UnitTests.vcxproj.filters index fd685e86b1..511e700f67 100644 --- a/unittest/UnitTests.vcxproj.filters +++ b/unittest/UnitTests.vcxproj.filters @@ -11,6 +11,7 @@ Windows +