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