2016-08-07 17:59:35 -07:00
|
|
|
// Copyright (c) 2014- PPSSPP Project.
|
2014-03-29 21:58:38 +01:00
|
|
|
|
|
|
|
// 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 <map>
|
|
|
|
|
|
|
|
#include "Common/Log.h"
|
2017-04-04 11:09:29 +02:00
|
|
|
#include "Common/StringUtils.h"
|
2022-08-05 10:00:27 +02:00
|
|
|
#include "Common/GPU/Shader.h"
|
|
|
|
#include "Common/GPU/ShaderWriter.h"
|
2022-08-05 10:47:48 +02:00
|
|
|
#include "Common/Data/Convert/ColorConv.h"
|
2014-03-30 17:37:15 +02:00
|
|
|
#include "Core/Reporting.h"
|
2022-08-05 10:00:27 +02:00
|
|
|
#include "GPU/Common/DrawEngineCommon.h"
|
|
|
|
#include "GPU/Common/TextureCacheCommon.h"
|
2022-08-22 12:20:21 +02:00
|
|
|
#include "GPU/Common/TextureShaderCommon.h"
|
2014-09-17 22:31:18 +02:00
|
|
|
#include "GPU/Common/DepalettizeShaderCommon.h"
|
2014-03-29 21:58:38 +01:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
static const VaryingDef varyings[1] = {
|
2022-08-09 15:32:27 +02:00
|
|
|
{ "vec2", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" },
|
2022-08-05 10:00:27 +02:00
|
|
|
};
|
2020-04-07 22:49:07 -07:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
static const SamplerDef samplers[2] = {
|
|
|
|
{ "tex" },
|
|
|
|
{ "pal" },
|
|
|
|
};
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
TextureShaderCache::TextureShaderCache(Draw::DrawContext *draw) : draw_(draw) { }
|
2015-03-15 13:15:16 -07:00
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
TextureShaderCache::~TextureShaderCache() {
|
2022-08-05 10:00:27 +02:00
|
|
|
DeviceLost();
|
2015-03-15 13:15:16 -07:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
void TextureShaderCache::DeviceRestore(Draw::DrawContext *draw) {
|
2022-08-05 10:00:27 +02:00
|
|
|
draw_ = draw;
|
2018-05-29 23:07:22 +02:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
void TextureShaderCache::DeviceLost() {
|
2022-08-05 10:00:27 +02:00
|
|
|
Clear();
|
|
|
|
}
|
|
|
|
|
2022-08-21 23:46:01 +02:00
|
|
|
ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const u32 clutHash, u32 *rawClut) {
|
2022-08-22 12:20:21 +02:00
|
|
|
// Simplistic, but works well enough.
|
|
|
|
u32 clutId = clutHash ^ (uint32_t)clutFormat;
|
2014-05-10 12:33:33 -07:00
|
|
|
|
2017-11-05 10:40:21 +01:00
|
|
|
auto oldtex = texCache_.find(clutId);
|
2014-03-29 21:58:38 +01:00
|
|
|
if (oldtex != texCache_.end()) {
|
2014-05-31 13:08:17 -07:00
|
|
|
oldtex->second->lastFrame = gpuStats.numFlips;
|
2022-08-21 23:46:01 +02:00
|
|
|
return *oldtex->second;
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2022-08-21 23:46:01 +02:00
|
|
|
int maxClutEntries = clutFormat == GE_CMODE_32BIT_ABGR8888 ? 256 : 512;
|
2014-05-10 12:33:33 -07:00
|
|
|
|
2022-08-22 12:28:46 +02:00
|
|
|
ClutTexture *tex = new ClutTexture();
|
2014-06-02 12:08:49 +10:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
Draw::TextureDesc desc{};
|
2022-08-21 23:46:01 +02:00
|
|
|
desc.width = maxClutEntries;
|
2022-08-05 10:00:27 +02:00
|
|
|
desc.height = 1;
|
|
|
|
desc.depth = 1;
|
|
|
|
desc.mipLevels = 1;
|
|
|
|
desc.tag = "clut";
|
|
|
|
desc.type = Draw::TextureType::LINEAR2D; // TODO: Try LINEAR1D?
|
|
|
|
desc.format = Draw::DataFormat::R8G8B8A8_UNORM; // TODO: Also support an BGR format. We won't bother with the 16-bit formats here.
|
|
|
|
|
|
|
|
uint8_t convTemp[2048]{};
|
|
|
|
|
|
|
|
switch (clutFormat) {
|
|
|
|
case GEPaletteFormat::GE_CMODE_32BIT_ABGR8888:
|
|
|
|
desc.initData.push_back((const uint8_t *)rawClut);
|
|
|
|
break;
|
|
|
|
case GEPaletteFormat::GE_CMODE_16BIT_BGR5650:
|
2022-08-21 23:46:01 +02:00
|
|
|
ConvertRGB565ToRGBA8888((u32 *)convTemp, (const u16 *)rawClut, maxClutEntries);
|
2022-08-05 10:00:27 +02:00
|
|
|
desc.initData.push_back(convTemp);
|
|
|
|
break;
|
|
|
|
case GEPaletteFormat::GE_CMODE_16BIT_ABGR5551:
|
2022-08-21 23:46:01 +02:00
|
|
|
ConvertRGBA5551ToRGBA8888((u32 *)convTemp, (const u16 *)rawClut, maxClutEntries);
|
2022-08-05 10:00:27 +02:00
|
|
|
desc.initData.push_back(convTemp);
|
|
|
|
break;
|
|
|
|
case GEPaletteFormat::GE_CMODE_16BIT_ABGR4444:
|
2022-08-21 23:46:01 +02:00
|
|
|
ConvertRGBA4444ToRGBA8888((u32 *)convTemp, (const u16 *)rawClut, maxClutEntries);
|
2022-08-05 10:00:27 +02:00
|
|
|
desc.initData.push_back(convTemp);
|
|
|
|
break;
|
|
|
|
}
|
2014-03-29 21:58:38 +01:00
|
|
|
|
2022-08-21 23:46:01 +02:00
|
|
|
int lastR = 0;
|
|
|
|
int lastG = 0;
|
|
|
|
int lastB = 0;
|
|
|
|
int lastA = 0;
|
|
|
|
|
|
|
|
int rampLength = 0;
|
|
|
|
// Quick check for how many continouosly growing entries we have at the start.
|
|
|
|
// Bilinearly filtering CLUTs only really makes sense for this kind of ramp.
|
|
|
|
for (int i = 0; i < maxClutEntries; i++) {
|
|
|
|
rampLength = i + 1;
|
|
|
|
int r = desc.initData[0][i * 4];
|
|
|
|
int g = desc.initData[0][i * 4 + 1];
|
|
|
|
int b = desc.initData[0][i * 4 + 2];
|
|
|
|
int a = desc.initData[0][i * 4 + 3];
|
|
|
|
if (r < lastR || g < lastG || b < lastB || a < lastA) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
lastR = r;
|
|
|
|
lastG = g;
|
|
|
|
lastB = b;
|
|
|
|
lastA = a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
tex->texture = draw_->CreateTexture(desc);
|
2014-05-31 13:08:17 -07:00
|
|
|
tex->lastFrame = gpuStats.numFlips;
|
2022-08-21 23:46:01 +02:00
|
|
|
tex->rampLength = rampLength;
|
2022-08-05 10:00:27 +02:00
|
|
|
|
2017-11-05 10:40:21 +01:00
|
|
|
texCache_[clutId] = tex;
|
2022-08-21 23:46:01 +02:00
|
|
|
return *tex;
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
void TextureShaderCache::Clear() {
|
|
|
|
for (auto shader = depalCache_.begin(); shader != depalCache_.end(); ++shader) {
|
2022-08-05 10:00:27 +02:00
|
|
|
if (shader->second->pipeline) {
|
|
|
|
shader->second->pipeline->Release();
|
2015-03-15 13:15:16 -07:00
|
|
|
}
|
2014-03-30 11:43:23 +02:00
|
|
|
delete shader->second;
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
2022-08-22 12:20:21 +02:00
|
|
|
depalCache_.clear();
|
2014-03-30 11:43:23 +02:00
|
|
|
for (auto tex = texCache_.begin(); tex != texCache_.end(); ++tex) {
|
2022-08-05 10:00:27 +02:00
|
|
|
tex->second->texture->Release();
|
2014-03-30 11:43:23 +02:00
|
|
|
delete tex->second;
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
2014-03-30 17:37:15 +02:00
|
|
|
texCache_.clear();
|
2015-03-15 17:05:23 -07:00
|
|
|
if (vertexShader_) {
|
2022-08-05 10:00:27 +02:00
|
|
|
vertexShader_->Release();
|
|
|
|
vertexShader_ = nullptr;
|
|
|
|
}
|
|
|
|
if (nearestSampler_) {
|
|
|
|
nearestSampler_->Release();
|
|
|
|
nearestSampler_ = nullptr;
|
2015-03-15 17:05:23 -07:00
|
|
|
}
|
2022-08-22 11:07:25 +02:00
|
|
|
if (linearSampler_) {
|
|
|
|
linearSampler_->Release();
|
|
|
|
linearSampler_ = nullptr;
|
|
|
|
}
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
void TextureShaderCache::Decimate() {
|
2014-05-31 13:08:17 -07:00
|
|
|
for (auto tex = texCache_.begin(); tex != texCache_.end(); ) {
|
|
|
|
if (tex->second->lastFrame + DEPAL_TEXTURE_OLD_AGE < gpuStats.numFlips) {
|
2022-08-05 10:00:27 +02:00
|
|
|
tex->second->texture->Release();
|
2014-05-31 13:08:17 -07:00
|
|
|
delete tex->second;
|
|
|
|
texCache_.erase(tex++);
|
|
|
|
} else {
|
|
|
|
++tex;
|
|
|
|
}
|
|
|
|
}
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2022-08-22 11:07:25 +02:00
|
|
|
Draw::SamplerState *TextureShaderCache::GetSampler(bool linearFilter) {
|
|
|
|
if (linearFilter) {
|
|
|
|
if (!linearSampler_) {
|
|
|
|
Draw::SamplerStateDesc desc{};
|
|
|
|
desc.magFilter = Draw::TextureFilter::LINEAR;
|
|
|
|
desc.minFilter = Draw::TextureFilter::LINEAR;
|
|
|
|
desc.wrapU = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
desc.wrapV = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
desc.wrapW = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
linearSampler_ = draw_->CreateSamplerState(desc);
|
|
|
|
}
|
|
|
|
return linearSampler_;
|
|
|
|
} else {
|
|
|
|
if (!nearestSampler_) {
|
|
|
|
Draw::SamplerStateDesc desc{};
|
|
|
|
desc.wrapU = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
desc.wrapV = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
desc.wrapW = Draw::TextureAddressMode::CLAMP_TO_EDGE;
|
|
|
|
nearestSampler_ = draw_->CreateSamplerState(desc);
|
|
|
|
}
|
|
|
|
return nearestSampler_;
|
2022-08-05 10:00:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
TextureShader *TextureShaderCache::CreateShader(const char *fs) {
|
2022-08-05 10:00:27 +02:00
|
|
|
using namespace Draw;
|
2018-01-20 08:35:24 -08:00
|
|
|
if (!vertexShader_) {
|
2022-08-22 12:20:21 +02:00
|
|
|
char *buffer = new char[4096];
|
|
|
|
GenerateVs(buffer, draw_->GetShaderLanguageDesc());
|
2022-08-05 10:00:27 +02:00
|
|
|
vertexShader_ = draw_->CreateShaderModule(ShaderStage::Vertex, draw_->GetShaderLanguageDesc().shaderLanguage, (const uint8_t *)buffer, strlen(buffer), "depal_vs");
|
2022-08-22 12:20:21 +02:00
|
|
|
delete[] buffer;
|
2015-03-15 13:15:16 -07:00
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
ShaderModule *fragShader = draw_->CreateShaderModule(ShaderStage::Fragment, draw_->GetShaderLanguageDesc().shaderLanguage, (const uint8_t *)fs, strlen(fs), "depal_fs");
|
2014-03-30 00:11:01 +01:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
static const InputLayoutDesc desc = {
|
|
|
|
{
|
|
|
|
{ 16, false },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{ 0, SEM_POSITION, DataFormat::R32G32_FLOAT, 0 },
|
|
|
|
{ 0, SEM_TEXCOORD0, DataFormat::R32G32_FLOAT, 8 },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
InputLayout *inputLayout = draw_->CreateInputLayout(desc);
|
|
|
|
BlendState *blendOff = draw_->CreateBlendState({ false, 0xF });
|
|
|
|
DepthStencilStateDesc dsDesc{};
|
|
|
|
DepthStencilState *noDepthStencil = draw_->CreateDepthStencilState(dsDesc);
|
|
|
|
RasterState *rasterNoCull = draw_->CreateRasterState({});
|
|
|
|
|
|
|
|
PipelineDesc depalPipelineDesc{
|
|
|
|
Primitive::TRIANGLE_STRIP, // Could have use a single triangle too (in which case we'd use LIST here) but want to be prepared to do subrectangles.
|
|
|
|
{ vertexShader_, fragShader },
|
|
|
|
inputLayout, noDepthStencil, blendOff, rasterNoCull, nullptr, samplers
|
|
|
|
};
|
2022-08-22 12:20:21 +02:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
Pipeline *pipeline = draw_->CreateGraphicsPipeline(depalPipelineDesc);
|
2014-03-29 21:58:38 +01:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
inputLayout->Release();
|
|
|
|
blendOff->Release();
|
|
|
|
noDepthStencil->Release();
|
|
|
|
rasterNoCull->Release();
|
2022-08-22 12:20:21 +02:00
|
|
|
fragShader->Release();
|
2014-03-29 21:58:38 +01:00
|
|
|
|
2022-08-05 10:00:27 +02:00
|
|
|
_assert_(pipeline);
|
2014-03-29 21:58:38 +01:00
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
TextureShader *depal = new TextureShader();
|
2022-08-05 10:00:27 +02:00
|
|
|
depal->pipeline = pipeline;
|
2022-08-22 12:20:21 +02:00
|
|
|
depal->code = fs; // to std::string
|
|
|
|
return depal;
|
|
|
|
}
|
2015-03-15 13:15:16 -07:00
|
|
|
|
2022-08-22 11:07:25 +02:00
|
|
|
TextureShader *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETextureFormat textureFormat, GEBufferFormat bufferFormat, bool smoothedDepal) {
|
2022-08-22 12:20:21 +02:00
|
|
|
using namespace Draw;
|
|
|
|
|
|
|
|
// Generate an ID for depal shaders.
|
|
|
|
u32 id = (clutMode & 0xFFFFFF) | (textureFormat << 24) | (bufferFormat << 28);
|
|
|
|
|
|
|
|
auto shader = depalCache_.find(id);
|
|
|
|
if (shader != depalCache_.end()) {
|
|
|
|
TextureShader *depal = shader->second;
|
|
|
|
return shader->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Parse these out of clutMode some nice way, to become a bit more stateless.
|
|
|
|
DepalConfig config;
|
|
|
|
config.clutFormat = gstate.getClutPaletteFormat();
|
|
|
|
config.startPos = gstate.getClutIndexStartPos();
|
|
|
|
config.shift = gstate.getClutIndexShift();
|
|
|
|
config.mask = gstate.getClutIndexMask();
|
|
|
|
config.bufferFormat = bufferFormat;
|
|
|
|
config.textureFormat = textureFormat;
|
2022-08-22 11:07:25 +02:00
|
|
|
config.smoothedDepal = smoothedDepal;
|
2022-08-22 12:20:21 +02:00
|
|
|
|
|
|
|
char *buffer = new char[4096];
|
|
|
|
GenerateDepalFs(buffer, config, draw_->GetShaderLanguageDesc());
|
|
|
|
TextureShader *ts = CreateShader(buffer);
|
2014-03-30 11:43:23 +02:00
|
|
|
delete[] buffer;
|
2022-08-22 12:20:21 +02:00
|
|
|
|
|
|
|
depalCache_[id] = ts;
|
|
|
|
|
|
|
|
return ts->pipeline ? ts : nullptr;
|
2014-03-29 21:58:38 +01:00
|
|
|
}
|
2017-04-04 11:09:29 +02:00
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
std::vector<std::string> TextureShaderCache::DebugGetShaderIDs(DebugShaderType type) {
|
2017-04-04 11:09:29 +02:00
|
|
|
std::vector<std::string> ids;
|
2022-08-22 12:20:21 +02:00
|
|
|
for (auto &iter : depalCache_) {
|
2017-04-04 11:09:29 +02:00
|
|
|
ids.push_back(StringFromFormat("%08x", iter.first));
|
|
|
|
}
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
std::string TextureShaderCache::DebugGetShaderString(std::string idstr, DebugShaderType type, DebugShaderStringType stringType) {
|
2017-04-04 11:09:29 +02:00
|
|
|
uint32_t id;
|
|
|
|
sscanf(idstr.c_str(), "%08x", &id);
|
2022-08-22 12:20:21 +02:00
|
|
|
auto iter = depalCache_.find(id);
|
|
|
|
if (iter == depalCache_.end())
|
2017-04-04 11:09:29 +02:00
|
|
|
return "";
|
|
|
|
switch (stringType) {
|
|
|
|
case SHADER_STRING_SHORT_DESC:
|
|
|
|
return idstr;
|
|
|
|
case SHADER_STRING_SOURCE_CODE:
|
|
|
|
return iter->second->code;
|
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
2022-08-22 11:45:52 +02:00
|
|
|
|
2022-08-22 12:20:21 +02:00
|
|
|
void TextureShaderCache::ApplyShader(TextureShader *shader, float bufferW, float bufferH, int renderW, int renderH, const KnownVertexBounds &bounds, u32 uoff, u32 voff) {
|
2022-08-22 11:45:52 +02:00
|
|
|
Draw2DVertex verts[4] = {
|
|
|
|
{-1, -1, 0, 0 },
|
|
|
|
{ 1, -1, 1, 0 },
|
|
|
|
{-1, 1, 0, 1 },
|
|
|
|
{ 1, 1, 1, 1 },
|
|
|
|
};
|
|
|
|
|
|
|
|
// If min is not < max, then we don't have values (wasn't set during decode.)
|
|
|
|
if (bounds.minV < bounds.maxV) {
|
|
|
|
const float invWidth = 1.0f / bufferW;
|
|
|
|
const float invHeight = 1.0f / bufferH;
|
|
|
|
// Inverse of half = double.
|
|
|
|
const float invHalfWidth = invWidth * 2.0f;
|
|
|
|
const float invHalfHeight = invHeight * 2.0f;
|
|
|
|
|
|
|
|
const int u1 = bounds.minU + uoff;
|
|
|
|
const int v1 = bounds.minV + voff;
|
|
|
|
const int u2 = bounds.maxU + uoff;
|
|
|
|
const int v2 = bounds.maxV + voff;
|
|
|
|
|
|
|
|
const float left = u1 * invHalfWidth - 1.0f;
|
|
|
|
const float right = u2 * invHalfWidth - 1.0f;
|
|
|
|
const float top = v1 * invHalfHeight - 1.0f;
|
|
|
|
const float bottom = v2 * invHalfHeight - 1.0f;
|
|
|
|
|
|
|
|
const float uvleft = u1 * invWidth;
|
|
|
|
const float uvright = u2 * invWidth;
|
|
|
|
const float uvtop = v1 * invHeight;
|
|
|
|
const float uvbottom = v2 * invHeight;
|
|
|
|
|
|
|
|
// Points are: BL, BR, TR, TL.
|
|
|
|
verts[0] = Draw2DVertex{ left, bottom, uvleft, uvbottom };
|
|
|
|
verts[1] = Draw2DVertex{ right, bottom, uvright, uvbottom };
|
|
|
|
verts[2] = Draw2DVertex{ left, top, uvleft, uvtop };
|
|
|
|
verts[3] = Draw2DVertex{ right, top, uvright, uvtop };
|
|
|
|
|
|
|
|
// We need to reapply the texture next time since we cropped UV.
|
|
|
|
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
|
|
|
|
}
|
|
|
|
|
|
|
|
Draw::Viewport vp{ 0.0f, 0.0f, (float)renderW, (float)renderH, 0.0f, 1.0f };
|
|
|
|
draw_->BindPipeline(shader->pipeline);
|
|
|
|
draw_->SetViewports(1, &vp);
|
|
|
|
draw_->SetScissorRect(0, 0, renderW, renderH);
|
|
|
|
draw_->DrawUP((const uint8_t *)verts, 4);
|
|
|
|
}
|