mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
1550 lines
66 KiB
C++
1550 lines
66 KiB
C++
// Vita3K emulator project
|
|
// Copyright (C) 2021 Vita3K team
|
|
// Copyright (c) 2002-2011 The ANGLE Project Authors.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// 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 for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#include <shader/spirv_recompiler.h>
|
|
#include <shader/usse_disasm.h>
|
|
#include <shader/usse_program_analyzer.h>
|
|
#include <shader/usse_utilities.h>
|
|
|
|
#include <gxm/functions.h>
|
|
#include <gxm/types.h>
|
|
#include <shader/gxp_parser.h>
|
|
#include <shader/profile.h>
|
|
#include <shader/usse_translator_entry.h>
|
|
#include <shader/usse_translator_types.h>
|
|
#include <shader/usse_utilities.h>
|
|
#include <util/fs.h>
|
|
#include <util/log.h>
|
|
#include <util/overloaded.h>
|
|
|
|
#include <SPIRV/SpvBuilder.h>
|
|
#include <SPIRV/disassemble.h>
|
|
#include <spirv_glsl.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
static constexpr bool LOG_SHADER_CODE = true;
|
|
static constexpr bool DUMP_SPIRV_BINARIES = false;
|
|
|
|
using namespace shader::usse;
|
|
|
|
namespace shader {
|
|
|
|
// **************
|
|
// * Constants *
|
|
// **************
|
|
|
|
static constexpr int REG_PA_COUNT = 32 * 4;
|
|
static constexpr int REG_SA_COUNT = 32 * 4;
|
|
static constexpr int REG_I_COUNT = 3 * 4;
|
|
static constexpr int REG_TEMP_COUNT = 20 * 4;
|
|
static constexpr int REG_INDEX_COUNT = 2 * 4;
|
|
static constexpr int REG_PRED_COUNT = 4 * 4;
|
|
static constexpr int REG_O_COUNT = 20 * 4;
|
|
|
|
// **************
|
|
// * Prototypes *
|
|
// **************
|
|
|
|
static spv::Id get_type_fallback(spv::Builder &b);
|
|
|
|
// ******************
|
|
// * Helper structs *
|
|
// ******************
|
|
|
|
// Keeps track of current struct declaration
|
|
// TODO: Handle struct arrays and multiple struct instances.
|
|
// The current (and the former) approach is quite naive, in that it assumes:
|
|
// 1) there is only one struct instance per declared struct
|
|
// 2) there are no struct array instances
|
|
struct StructDeclContext {
|
|
std::string name;
|
|
usse::RegisterBank reg_type = usse::RegisterBank::INVALID;
|
|
std::vector<spv::Id> field_ids;
|
|
std::vector<std::string> field_names; // count must be equal to `field_ids`
|
|
bool is_interaface_block{ false };
|
|
|
|
bool empty() const { return name.empty(); }
|
|
void clear() { *this = {}; }
|
|
};
|
|
|
|
// TODO do we need this? This is made to avoid spir-v validation error regarding interface variables
|
|
struct VarToReg {
|
|
spv::Id var;
|
|
bool pa; // otherwise sa
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
DataType dtype;
|
|
};
|
|
|
|
struct TranslationState {
|
|
std::string hash;
|
|
spv::Id last_frag_data_id = spv::NoResult;
|
|
spv::Id color_attachment_id = spv::NoResult;
|
|
spv::Id mask_id = spv::NoResult;
|
|
spv::Id frag_coord_id = spv::NoResult; ///< gl_FragCoord, not built-in in SPIR-V.
|
|
spv::Id render_info_id = spv::NoResult;
|
|
std::vector<VarToReg> var_to_regs;
|
|
std::vector<spv::Id> interfaces;
|
|
bool is_maskupdate{};
|
|
bool is_fragment{};
|
|
bool should_gl_spirv_compatible{};
|
|
};
|
|
|
|
struct VertexProgramOutputProperties {
|
|
std::string name;
|
|
std::uint32_t component_count;
|
|
std::uint32_t location;
|
|
|
|
VertexProgramOutputProperties()
|
|
: name(nullptr)
|
|
, component_count(0)
|
|
, location(0) {}
|
|
|
|
VertexProgramOutputProperties(const char *name, std::uint32_t component_count, std::uint32_t location)
|
|
: name(name)
|
|
, component_count(component_count)
|
|
, location(location) {}
|
|
};
|
|
using VertexProgramOutputPropertiesMap = std::map<SceGxmVertexProgramOutputs, VertexProgramOutputProperties>;
|
|
|
|
// ******************************
|
|
// * Functions (implementation) *
|
|
// ******************************
|
|
|
|
static spv::Id create_array_if_needed(spv::Builder &b, const spv::Id param_id, const Input &input, const uint32_t explicit_array_size = 0) {
|
|
// disabled
|
|
return param_id;
|
|
|
|
const auto array_size = explicit_array_size == 0 ? input.array_size : explicit_array_size;
|
|
if (array_size > 1) {
|
|
const auto array_size_id = b.makeUintConstant(array_size);
|
|
return b.makeArrayType(param_id, array_size_id, 0);
|
|
}
|
|
return param_id;
|
|
}
|
|
|
|
static spv::Id get_type_basic(spv::Builder &b, const Input &input) {
|
|
switch (input.type) {
|
|
// clang-format off
|
|
case DataType::F16:
|
|
case DataType::F32:
|
|
return b.makeFloatType(32);
|
|
|
|
case DataType::UINT8:
|
|
case DataType::UINT16:
|
|
case DataType::UINT32:
|
|
return b.makeUintType(32);
|
|
|
|
case DataType::INT8:
|
|
case DataType::INT16:
|
|
case DataType::INT32:
|
|
return b.makeIntType(32);
|
|
|
|
// clang-format on
|
|
default: {
|
|
LOG_ERROR("Unsupported parameter type {} used in shader.", log_hex(input.type));
|
|
return get_type_fallback(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
static spv::Id get_type_fallback(spv::Builder &b) {
|
|
return b.makeFloatType(32);
|
|
}
|
|
|
|
static spv::Id get_type_scalar(spv::Builder &b, const Input &input) {
|
|
spv::Id param_id = get_type_basic(b, input);
|
|
param_id = create_array_if_needed(b, param_id, input);
|
|
return param_id;
|
|
}
|
|
|
|
static spv::Id get_type_vector(spv::Builder &b, const Input &input) {
|
|
if (input.component_count == 1) {
|
|
return get_type_scalar(b, input);
|
|
}
|
|
spv::Id param_id = get_type_basic(b, input);
|
|
param_id = b.makeVectorType(param_id, input.component_count);
|
|
|
|
return param_id;
|
|
}
|
|
|
|
static spv::Id get_type_array(spv::Builder &b, const Input &input) {
|
|
spv::Id param_id = get_type_basic(b, input);
|
|
if (input.component_count > 1) {
|
|
param_id = b.makeVectorType(param_id, input.component_count);
|
|
}
|
|
|
|
// TODO: Stride
|
|
param_id = b.makeArrayType(param_id, b.makeUintConstant(input.array_size), 1);
|
|
|
|
return param_id;
|
|
}
|
|
|
|
static spv::Id get_param_type(spv::Builder &b, const Input &input) {
|
|
switch (input.generic_type) {
|
|
case GenericType::SCALER:
|
|
return get_type_scalar(b, input);
|
|
case GenericType::VECTOR:
|
|
return get_type_vector(b, input);
|
|
case GenericType::ARRAY:
|
|
return get_type_array(b, input);
|
|
default:
|
|
return get_type_fallback(b);
|
|
}
|
|
}
|
|
|
|
spv::StorageClass reg_type_to_spv_storage_class(usse::RegisterBank reg_type) {
|
|
switch (reg_type) {
|
|
case usse::RegisterBank::TEMP:
|
|
return spv::StorageClassPrivate;
|
|
case usse::RegisterBank::PRIMATTR:
|
|
return spv::StorageClassInput;
|
|
case usse::RegisterBank::OUTPUT:
|
|
return spv::StorageClassOutput;
|
|
case usse::RegisterBank::SECATTR:
|
|
return spv::StorageClassUniformConstant;
|
|
case usse::RegisterBank::FPINTERNAL:
|
|
return spv::StorageClassPrivate;
|
|
|
|
case usse::RegisterBank::SPECIAL: break;
|
|
case usse::RegisterBank::GLOBAL: break;
|
|
case usse::RegisterBank::FPCONSTANT: break;
|
|
case usse::RegisterBank::IMMEDIATE: break;
|
|
case usse::RegisterBank::INDEX: break;
|
|
case usse::RegisterBank::INDEXED1: break;
|
|
case usse::RegisterBank::INDEXED2: break;
|
|
|
|
case usse::RegisterBank::MAXIMUM:
|
|
case usse::RegisterBank::INVALID:
|
|
default:
|
|
return spv::StorageClassMax;
|
|
}
|
|
|
|
LOG_WARN("Unsupported reg_type {}", static_cast<uint32_t>(reg_type));
|
|
return spv::StorageClassMax;
|
|
}
|
|
|
|
static spv::Id create_param_sampler(spv::Builder &b, const std::string &name, const spv::Dim dim_type) {
|
|
spv::Id sampled_type = b.makeFloatType(32);
|
|
spv::Id image_type = b.makeImageType(sampled_type, dim_type, false, false, false, 1, spv::ImageFormatUnknown);
|
|
spv::Id sampled_image_type = b.makeSampledImageType(image_type);
|
|
return b.createVariable(spv::StorageClassUniformConstant, sampled_image_type, name.c_str());
|
|
}
|
|
|
|
static spv::Id create_input_variable(spv::Builder &b, SpirvShaderParameters ¶meters, utils::SpirvUtilFunctions &utils, const FeatureState &features, const char *name, const RegisterBank bank, const std::uint32_t offset, spv::Id type, const std::uint32_t size, spv::Id force_id = spv::NoResult, DataType dtype = DataType::F32) {
|
|
std::uint32_t total_var_comp = size / 4;
|
|
spv::Id var = !force_id ? (b.createVariable(reg_type_to_spv_storage_class(bank), type, name)) : force_id;
|
|
Operand dest;
|
|
dest.bank = bank;
|
|
dest.num = offset;
|
|
dest.type = dtype;
|
|
|
|
Imm4 dest_mask = 0b1111;
|
|
|
|
auto get_dest_mask = [&]() {
|
|
switch (total_var_comp) {
|
|
case 1:
|
|
dest_mask = 0b1;
|
|
break;
|
|
|
|
case 2:
|
|
dest_mask = 0b11;
|
|
break;
|
|
|
|
case 3:
|
|
dest_mask = 0b111;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
if (total_var_comp != 1 && b.isArrayType(b.getContainedTypeId(b.getTypeId(var)))) {
|
|
spv::Id arr_type = b.getContainedTypeId(b.getTypeId(var));
|
|
spv::Id comp_type = b.getContainedTypeId(arr_type);
|
|
total_var_comp = b.getNumTypeComponents(comp_type);
|
|
|
|
// Reget the mask
|
|
get_dest_mask();
|
|
|
|
for (auto i = 0; i < b.getNumTypeComponents(arr_type); i++) {
|
|
spv::Id elm = b.createOp(spv::OpAccessChain, b.makePointer(spv::StorageClassPrivate, comp_type),
|
|
{ var, b.makeIntConstant(i) });
|
|
utils::store(b, parameters, utils, features, dest, b.createLoad(elm), dest_mask, 0 + i * 4);
|
|
}
|
|
} else {
|
|
get_dest_mask();
|
|
|
|
if (!b.isConstant(var)) {
|
|
var = b.createLoad(var);
|
|
var = utils::finalize(b, var, var, SWIZZLE_CHANNEL_4_DEFAULT, 0, dest_mask);
|
|
}
|
|
|
|
utils::store(b, parameters, utils, features, dest, var, dest_mask, 0);
|
|
}
|
|
|
|
return var;
|
|
}
|
|
|
|
static spv::Id create_builtin_sampler(spv::Builder &b, const FeatureState &features, TranslationState &translation_state, const std::string &name) {
|
|
spv::Id f32 = b.makeFloatType(32);
|
|
spv::Id v4 = b.makeVectorType(f32, 4);
|
|
spv::Id sampled_type = b.makeFloatType(32);
|
|
|
|
int sampled = 2;
|
|
spv::ImageFormat img_format = spv::ImageFormatRgba8;
|
|
|
|
spv::Id image_type = b.makeImageType(sampled_type, spv::Dim2D, false, false, false, sampled, img_format);
|
|
spv::Id sampler = b.createVariable(spv::StorageClassUniformConstant, image_type, name.c_str());
|
|
translation_state.interfaces.push_back(sampler);
|
|
|
|
return sampler;
|
|
}
|
|
|
|
static void create_fragment_inputs(spv::Builder &b, SpirvShaderParameters ¶meters, utils::SpirvUtilFunctions &utils, const FeatureState &features, TranslationState &translation_state, NonDependentTextureQueryCallInfos &tex_query_infos, SamplerMap &samplers,
|
|
const SceGxmProgram &program) {
|
|
static const std::unordered_map<std::uint32_t, std::pair<std::string, std::uint32_t>> name_map = {
|
|
{ 0xD000, { "v_Position", 0 } },
|
|
{ 0xC000, { "v_Fog", 3 } },
|
|
{ 0xA000, { "v_Color0", 1 } },
|
|
{ 0xB000, { "v_Color1", 2 } },
|
|
{ 0x0, { "v_TexCoord0", 4 } },
|
|
{ 0x1000, { "v_TexCoord1", 5 } },
|
|
{ 0x2000, { "v_TexCoord2", 6 } },
|
|
{ 0x3000, { "v_TexCoord3", 7 } },
|
|
{ 0x4000, { "v_TexCoord4", 8 } },
|
|
{ 0x5000, { "v_TexCoord5", 9 } },
|
|
{ 0x6000, { "v_TexCoord6", 10 } },
|
|
{ 0x7000, { "v_TexCoord7", 11 } },
|
|
{ 0x8000, { "v_TexCoord8", 12 } },
|
|
{ 0x9000, { "v_TexCoord9", 13 } },
|
|
};
|
|
|
|
// Both vertex output and this struct should stay in a larger varying struct
|
|
auto vertex_varyings_ptr = program.vertex_varyings();
|
|
|
|
const SceGxmProgramAttributeDescriptor *descriptor = reinterpret_cast<const SceGxmProgramAttributeDescriptor *>(
|
|
reinterpret_cast<const std::uint8_t *>(&vertex_varyings_ptr->vertex_outputs1) + vertex_varyings_ptr->vertex_outputs1);
|
|
|
|
std::uint32_t pa_offset = 0;
|
|
std::uint32_t anon_tex_count = 0;
|
|
|
|
const SceGxmProgramParameter *const gxp_parameters = gxp::program_parameters(program);
|
|
|
|
// Store the coords
|
|
std::array<shader::usse::Coord, 11> coords;
|
|
|
|
spv::Id f32 = b.makeFloatType(32);
|
|
spv::Id v4 = b.makeVectorType(f32, 4);
|
|
|
|
spv::Id current_coord = b.createVariable(spv::StorageClassInput, v4, "gl_FragCoord");
|
|
b.addDecoration(current_coord, spv::DecorationBuiltIn, spv::BuiltInFragCoord);
|
|
|
|
translation_state.interfaces.push_back(current_coord);
|
|
translation_state.frag_coord_id = current_coord;
|
|
|
|
// It may actually be total fragments input
|
|
for (size_t i = 0; i < vertex_varyings_ptr->varyings_count; i++, descriptor++) {
|
|
// 4 bit flag indicates a PA!
|
|
if ((descriptor->attribute_info & 0x4000F000) != 0xF000) {
|
|
std::uint32_t input_id = (descriptor->attribute_info & 0x4000F000);
|
|
|
|
std::string pa_name;
|
|
std::uint32_t pa_loc = 0;
|
|
|
|
if (input_id & 0x40000000) {
|
|
pa_name = "v_SpriteCoord";
|
|
} else {
|
|
pa_name = name_map.at(input_id).first;
|
|
pa_loc = name_map.at(input_id).second;
|
|
}
|
|
|
|
std::string pa_type = "uchar";
|
|
DataType pa_dtype = DataType::UINT8;
|
|
|
|
uint32_t input_type = (descriptor->attribute_info & 0x30100000);
|
|
|
|
if (input_type == 0x20000000) {
|
|
pa_type = "half";
|
|
pa_dtype = DataType::F16;
|
|
} else if (input_type == 0x10000000) {
|
|
pa_type = "fixed";
|
|
// TODO: Supply data type
|
|
} else if (input_type == 0x100000) {
|
|
if (input_id == 0xA000 || input_id == 0xB000) {
|
|
pa_type = "float";
|
|
pa_dtype = DataType::F32;
|
|
}
|
|
} else if (input_id != 0xA000 && input_id != 0xB000) {
|
|
pa_type = "float";
|
|
pa_dtype = DataType::F32;
|
|
}
|
|
|
|
// Create PA Iterator SPIR-V variable
|
|
const auto num_comp = ((descriptor->attribute_info >> 22) & 3) + 1;
|
|
|
|
// Force this to 4. TODO: Don't
|
|
// Reason is for compability between vertex and fragment. This is like an anti-crash when linking.
|
|
// Fragment will only copy what it needed.
|
|
const auto pa_iter_type = b.makeVectorType(b.makeFloatType(32), 4);
|
|
const auto pa_iter_size = num_comp * 4;
|
|
spv::Id pa_iter_var = spv::NoResult;
|
|
|
|
// TODO how about centroid?
|
|
if (input_id == 0xD000) {
|
|
pa_iter_var = translation_state.frag_coord_id;
|
|
} else {
|
|
pa_iter_var = b.createVariable(spv::StorageClassInput, pa_iter_type, pa_name.c_str());
|
|
b.addDecoration(pa_iter_var, spv::DecorationLocation, pa_loc);
|
|
}
|
|
|
|
translation_state.var_to_regs.push_back(
|
|
{ pa_iter_var,
|
|
true,
|
|
pa_offset,
|
|
pa_iter_size,
|
|
pa_dtype });
|
|
translation_state.interfaces.push_back(pa_iter_var);
|
|
LOG_DEBUG("Iterator: pa{} = ({}{}) {}", pa_offset, pa_type, num_comp, pa_name);
|
|
|
|
bool do_coord = false;
|
|
|
|
if (input_id >= 0 && input_id <= 0x9000) {
|
|
input_id /= 0x1000;
|
|
do_coord = true;
|
|
} else if (input_id == 0xD000) {
|
|
// Not sure, comment out for now
|
|
// input_id = 10;
|
|
// do_coord = true;
|
|
}
|
|
|
|
if (do_coord) {
|
|
coords[input_id].first = pa_iter_var;
|
|
coords[input_id].second = static_cast<int>(DataType::F32);
|
|
}
|
|
|
|
pa_offset += ((descriptor->size >> 4) & 3) + 1;
|
|
}
|
|
|
|
uint32_t tex_coord_index = (descriptor->attribute_info & 0x40F);
|
|
|
|
// Process texture query variable (iterator), stored on a PA (primary attribute) register
|
|
if (tex_coord_index != 0xF) {
|
|
if (tex_coord_index == 0x400) {
|
|
// Texcoord variable
|
|
tex_coord_index = 10;
|
|
}
|
|
|
|
std::string tex_name = "";
|
|
std::string sampling_type = "2D";
|
|
spv::Dim dim_type = spv::Dim2D;
|
|
uint32_t sampler_resource_index = 0;
|
|
|
|
bool anonymous = false;
|
|
|
|
for (std::uint32_t p = 0; p < program.parameter_count; p++) {
|
|
const SceGxmProgramParameter ¶meter = gxp_parameters[p];
|
|
|
|
if (parameter.resource_index == descriptor->resource_index && parameter.category == SCE_GXM_PARAMETER_CATEGORY_SAMPLER) {
|
|
tex_name = gxp::parameter_name(parameter);
|
|
sampler_resource_index = parameter.resource_index;
|
|
|
|
if (parameter.is_sampler_cube()) {
|
|
dim_type = spv::DimCube;
|
|
sampling_type = "CUBE";
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tex_name.empty()) {
|
|
LOG_INFO("Sample symbol stripped, using anonymous name");
|
|
|
|
anonymous = true;
|
|
tex_name = fmt::format("anonymousTexture{}", anon_tex_count++);
|
|
}
|
|
|
|
const auto component_type = (descriptor->component_info >> 4) & 3;
|
|
const auto swizzle_texcoord = (descriptor->attribute_info & 0x300);
|
|
|
|
std::string component_type_str = "????";
|
|
DataType store_type = DataType::F16;
|
|
switch (component_type) {
|
|
case 0: {
|
|
component_type_str = "uchar";
|
|
store_type = DataType::UINT8;
|
|
break;
|
|
}
|
|
case 1: {
|
|
// Maybe char?
|
|
LOG_WARN("Unsupported texture component: {}", component_type);
|
|
break;
|
|
}
|
|
case 2: {
|
|
component_type_str = "half";
|
|
store_type = DataType::F16;
|
|
break;
|
|
}
|
|
case 3: {
|
|
component_type_str = "float";
|
|
store_type = DataType::F32;
|
|
break;
|
|
}
|
|
default: {
|
|
LOG_WARN("Unsupported texture component: {}", component_type);
|
|
}
|
|
}
|
|
|
|
std::string swizzle_str = ".xy";
|
|
std::string projecting;
|
|
|
|
if (swizzle_texcoord != 0x100) {
|
|
projecting = "proj";
|
|
}
|
|
|
|
int tex_coord_comp_count = 2;
|
|
|
|
if (swizzle_texcoord == 0x300) {
|
|
swizzle_str = ".xyz";
|
|
tex_coord_comp_count = 3;
|
|
} else if (swizzle_texcoord == 0x200) {
|
|
swizzle_str = ".xyw";
|
|
|
|
// Not really sure
|
|
tex_coord_comp_count = 4;
|
|
}
|
|
|
|
std::string centroid_str;
|
|
|
|
if ((descriptor->attribute_info & 0x10) == 0x10) {
|
|
centroid_str = "_CENTROID";
|
|
}
|
|
|
|
uint32_t num_component = 0;
|
|
|
|
if ((descriptor->component_info & 0x40) != 0x40) {
|
|
num_component = 4;
|
|
}
|
|
|
|
std::string texcoord_name = (tex_coord_index == 10) ? "POINTCOORD" : ("TEXCOORD" + std::to_string(tex_coord_index));
|
|
LOG_TRACE("pa{} = tex{}{}<{}{}>({}, {}{}{})", pa_offset, sampling_type, projecting,
|
|
component_type_str, num_component, tex_name, texcoord_name, centroid_str, swizzle_str);
|
|
|
|
std::string tex_query_var_name = "tex_query";
|
|
tex_query_var_name += std::to_string(tex_coord_index);
|
|
|
|
NonDependentTextureQueryCallInfo tex_query_info;
|
|
tex_query_info.store_type = static_cast<int>(store_type);
|
|
|
|
// Size of this extra pa occupied
|
|
// Force this to be PRIVATE
|
|
const auto size = ((descriptor->size >> 6) & 3) + 1;
|
|
tex_query_info.dest_offset = pa_offset;
|
|
|
|
tex_query_info.coord_index = tex_coord_index;
|
|
|
|
if (anonymous) {
|
|
// Probably not gonna be used in future, just for non-dependent queries
|
|
tex_query_info.sampler = create_param_sampler(b, (program.is_vertex() ? "vertTex_" : "fragTex_") + tex_name, dim_type);
|
|
} else {
|
|
tex_query_info.sampler = samplers[sampler_resource_index];
|
|
}
|
|
|
|
tex_query_infos.push_back(tex_query_info);
|
|
|
|
pa_offset += size;
|
|
}
|
|
}
|
|
|
|
static constexpr std::uint32_t TEXCOORD_BASE_LOCATION = 4;
|
|
|
|
for (auto &query_info : tex_query_infos) {
|
|
if (coords[query_info.coord_index].first == spv::NoResult) {
|
|
// Create an 'in' variable
|
|
// TODO: this really right?
|
|
std::string coord_name = "v_TexCoord";
|
|
|
|
if (query_info.coord_index == 10) {
|
|
coord_name = "gl_PointCoord";
|
|
} else {
|
|
coord_name += std::to_string(query_info.coord_index);
|
|
}
|
|
|
|
coords[query_info.coord_index].first = b.createVariable(spv::StorageClassInput,
|
|
b.makeVectorType(b.makeFloatType(32), /*tex_coord_comp_count*/ 4), coord_name.c_str());
|
|
|
|
if (query_info.coord_index == 10) {
|
|
b.addDecoration(coords[query_info.coord_index].first, spv::DecorationBuiltIn, spv::BuiltInPointCoord);
|
|
}
|
|
|
|
b.addDecoration(coords[query_info.coord_index].first, spv::DecorationLocation, TEXCOORD_BASE_LOCATION + query_info.coord_index);
|
|
translation_state.interfaces.push_back(coords[query_info.coord_index].first);
|
|
|
|
coords[query_info.coord_index].second = static_cast<int>(DataType::F32);
|
|
}
|
|
|
|
query_info.coord = coords[query_info.coord_index];
|
|
}
|
|
|
|
auto mask = create_builtin_sampler(b, features, translation_state, "f_mask");
|
|
translation_state.mask_id = mask;
|
|
|
|
b.addDecoration(mask, spv::DecorationBinding, MASK_TEXTURE_SLOT_IMAGE);
|
|
|
|
if (program.is_native_color()) {
|
|
// There might be a chance that this shader also reads from OUTPUT bank. We will load last state frag data
|
|
spv::Id source = spv::NoResult;
|
|
|
|
if (features.direct_fragcolor) {
|
|
// The GPU supports gl_LastFragData. It's only OpenGL though
|
|
// TODO: Make this not emit with OpenGL
|
|
spv::Id v4_a = b.makeArrayType(v4, b.makeIntConstant(1), 0);
|
|
spv::Id last_frag_data_arr = b.createVariable(spv::StorageClassInput, v4_a, "gl_LastFragData");
|
|
translation_state.interfaces.push_back(last_frag_data_arr);
|
|
spv::Id last_frag_data = b.createOp(spv::OpAccessChain, v4, { last_frag_data_arr, b.makeIntConstant(0) });
|
|
|
|
// Copy outs into. The output data from last stage should has the same format as our
|
|
source = last_frag_data;
|
|
translation_state.last_frag_data_id = last_frag_data_arr;
|
|
} else if (features.support_shader_interlock || features.support_texture_barrier) {
|
|
// Create a global sampler, which is our color attachment
|
|
auto color_attachment = create_builtin_sampler(b, features, translation_state, "f_colorAttachment");
|
|
|
|
b.addDecoration(color_attachment, spv::DecorationBinding, COLOR_ATTACHMENT_TEXTURE_SLOT_IMAGE);
|
|
translation_state.color_attachment_id = color_attachment;
|
|
|
|
spv::Id i32 = b.makeIntegerType(32, true);
|
|
current_coord = b.createUnaryOp(spv::OpConvertFToS, b.makeVectorType(i32, 4), b.createLoad(current_coord));
|
|
current_coord = b.createOp(spv::OpVectorShuffle, b.makeVectorType(i32, 2), { current_coord, current_coord, 0, 1 });
|
|
|
|
source = b.createOp(spv::OpImageRead, v4, { b.createLoad(color_attachment), current_coord });
|
|
} else {
|
|
// Try to initialize outs[0] to some nice value. In case the GPU has garbage data for our shader
|
|
spv::Id v4 = b.makeVectorType(b.makeFloatType(32), 4);
|
|
spv::Id rezero = b.makeFloatConstant(0.0f);
|
|
source = b.makeCompositeConstant(v4, { rezero, rezero, rezero, rezero });
|
|
}
|
|
|
|
if (source != spv::NoResult) {
|
|
Operand target_to_store;
|
|
|
|
target_to_store.bank = RegisterBank::OUTPUT;
|
|
target_to_store.num = 0;
|
|
target_to_store.type = std::get<0>(shader::get_parameter_type_store_and_name(program.get_fragment_output_type()));
|
|
if (!is_float_data_type(target_to_store.type)) {
|
|
source = utils::convert_to_int(b, source, target_to_store.type, true);
|
|
}
|
|
|
|
utils::store(b, parameters, utils, features, target_to_store, source, 0b1111, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Calculate variable size, in float granularity.
|
|
*/
|
|
static size_t calculate_variable_size(const SceGxmProgramParameter ¶meter, const DataType store_type) {
|
|
size_t element_size = shader::usse::get_data_type_size(store_type) * parameter.component_count;
|
|
|
|
if (parameter.array_size == 1) {
|
|
return (element_size + 3) / 4;
|
|
}
|
|
|
|
// Need to do alignment
|
|
if (parameter.component_count == 1) {
|
|
// Each element is a scalar. Apply 32-bit alignment.
|
|
// Assuming no scalar has larger size than 4.
|
|
return parameter.array_size;
|
|
}
|
|
|
|
// Apply 64-bit alignment
|
|
return parameter.array_size * (((element_size + 8 - (element_size & 7)) + 3) / 4);
|
|
}
|
|
|
|
// For uniform buffer resigned in registers
|
|
static void copy_uniform_block_to_register(spv::Builder &builder, spv::Id sa_bank, spv::Id block, spv::Id ite, const int start, const int vec4_count) {
|
|
int start_in_vec4_granularity = start / 4;
|
|
|
|
utils::make_for_loop(builder, ite, builder.makeIntConstant(0), builder.makeIntConstant(vec4_count), [&]() {
|
|
spv::Id to_copy = builder.createAccessChain(spv::StorageClassUniform, block, { builder.createLoad(ite) });
|
|
spv::Id dest = builder.createAccessChain(spv::StorageClassPrivate, sa_bank, { builder.createBinOp(spv::OpIAdd, builder.getTypeId(builder.createLoad(ite)), builder.createLoad(ite), builder.makeIntConstant(start_in_vec4_granularity)) });
|
|
spv::Id dest_friend = spv::NoResult;
|
|
|
|
if (start % 4 == 0) {
|
|
builder.createStore(builder.createLoad(to_copy), dest);
|
|
} else {
|
|
dest_friend = builder.createAccessChain(spv::StorageClassPrivate, sa_bank, { builder.createBinOp(spv::OpIAdd, builder.getTypeId(builder.createLoad(ite)), builder.createLoad(ite), builder.makeIntConstant(start_in_vec4_granularity + 1)) });
|
|
|
|
std::vector<spv::Id> ops_copy_1 = { dest, to_copy };
|
|
std::vector<spv::Id> ops_copy_2 = { dest_friend, to_copy };
|
|
|
|
for (int i = 0; i < start % 4; i++) {
|
|
ops_copy_1.push_back(i);
|
|
ops_copy_2.push_back(4 + (start % 4) + i);
|
|
}
|
|
|
|
for (int i = 0; i < (4 - start % 4); i++) {
|
|
ops_copy_1.push_back(4 + i);
|
|
ops_copy_2.push_back((start % 4) + i);
|
|
}
|
|
|
|
to_copy = builder.createOp(spv::OpVectorShuffle, builder.getTypeId(to_copy), ops_copy_1);
|
|
spv::Id to_copy_2 = builder.createOp(spv::OpVectorShuffle, builder.getTypeId(to_copy), ops_copy_2);
|
|
|
|
builder.createStore(builder.createLoad(to_copy), dest);
|
|
builder.createStore(builder.createLoad(to_copy_2), dest_friend);
|
|
}
|
|
});
|
|
}
|
|
|
|
static SpirvShaderParameters create_parameters(spv::Builder &b, const SceGxmProgram &program, utils::SpirvUtilFunctions &utils,
|
|
const FeatureState &features, TranslationState &translation_state, SceGxmProgramType program_type, NonDependentTextureQueryCallInfos &texture_queries,
|
|
const std::vector<SceGxmVertexAttribute> *hint_attributes) {
|
|
SpirvShaderParameters spv_params = {};
|
|
const SceGxmProgramParameter *const gxp_parameters = gxp::program_parameters(program);
|
|
|
|
// Make array type. TODO: Make length configurable
|
|
spv::Id f32_type = b.makeFloatType(32);
|
|
spv::Id i32_type = b.makeIntType(32);
|
|
spv::Id b_type = b.makeBoolType();
|
|
|
|
spv::Id f32_v4_type = b.makeVectorType(f32_type, 4);
|
|
spv::Id pa_arr_type = b.makeArrayType(f32_v4_type, b.makeIntConstant(REG_PA_COUNT / 4), 0);
|
|
spv::Id sa_arr_type = b.makeArrayType(f32_v4_type, b.makeIntConstant(REG_SA_COUNT / 4), 0);
|
|
spv::Id i_arr_type = b.makeArrayType(f32_v4_type, b.makeIntConstant(REG_I_COUNT / 4), 0);
|
|
spv::Id temp_arr_type = b.makeArrayType(f32_v4_type, b.makeIntConstant(REG_TEMP_COUNT / 4), 0);
|
|
spv::Id index_arr_type = b.makeArrayType(i32_type, b.makeIntConstant(REG_INDEX_COUNT / 4), 0);
|
|
spv::Id pred_arr_type = b.makeArrayType(b_type, b.makeIntConstant(REG_PRED_COUNT / 4), 0);
|
|
spv::Id o_arr_type = b.makeArrayType(f32_v4_type, b.makeIntConstant(REG_O_COUNT / 4), 0);
|
|
|
|
// Create register banks
|
|
spv_params.ins = b.createVariable(spv::StorageClassPrivate, pa_arr_type, "pa");
|
|
spv_params.uniforms = b.createVariable(spv::StorageClassPrivate, sa_arr_type, "sa");
|
|
spv_params.internals = b.createVariable(spv::StorageClassPrivate, i_arr_type, "internals");
|
|
spv_params.temps = b.createVariable(spv::StorageClassPrivate, temp_arr_type, "r");
|
|
spv_params.predicates = b.createVariable(spv::StorageClassPrivate, pred_arr_type, "p");
|
|
spv_params.indexes = b.createVariable(spv::StorageClassPrivate, index_arr_type, "idx");
|
|
spv_params.outs = b.createVariable(spv::StorageClassPrivate, o_arr_type, "outs");
|
|
|
|
SamplerMap samplers;
|
|
|
|
spv::Id ite_copy = b.createVariable(spv::StorageClassFunction, i32_type, "i");
|
|
|
|
using literal_pair = std::pair<std::uint32_t, spv::Id>;
|
|
|
|
std::vector<literal_pair> literal_pairs;
|
|
|
|
const auto program_input = shader::get_program_input(program);
|
|
|
|
std::map<int, int> buffer_bases;
|
|
std::map<int, std::uint32_t> buffer_sizes;
|
|
|
|
for (const auto &buffer : program_input.uniform_buffers) {
|
|
const auto buffer_size = (buffer.size + 3) / 4;
|
|
buffer_sizes.emplace(buffer.index, buffer_size);
|
|
}
|
|
|
|
int last_base = 0;
|
|
int total_members = 0;
|
|
|
|
if (!buffer_sizes.empty()) {
|
|
usse::SpirvCode buffer_container_member_types;
|
|
const bool is_vert = (program_type == SceGxmProgramType::Vertex);
|
|
|
|
for (auto &[buffer_index, buffer_size] : buffer_sizes) {
|
|
SpirvUniformBufferInfo buffer_info;
|
|
buffer_info.base = last_base;
|
|
buffer_info.size = buffer_size * 16;
|
|
buffer_info.index_in_container = total_members++;
|
|
|
|
buffer_bases.emplace(buffer_index, last_base);
|
|
spv_params.buffers.emplace(buffer_index, buffer_info);
|
|
|
|
last_base += buffer_size * 16;
|
|
|
|
spv::Id buffer_type_arr = b.makeArrayType(f32_v4_type, b.makeIntConstant(buffer_size), 16);
|
|
b.addDecoration(buffer_type_arr, spv::DecorationArrayStride, 16);
|
|
|
|
buffer_container_member_types.push_back(buffer_type_arr);
|
|
}
|
|
|
|
spv::Id buffer_container_type = b.makeStructType(buffer_container_member_types,
|
|
is_vert ? "vertexDataType" : "fragmentDataType");
|
|
|
|
b.addDecoration(buffer_container_type, spv::DecorationBlock);
|
|
b.addDecoration(buffer_container_type, spv::DecorationGLSLShared);
|
|
b.addDecoration(buffer_container_type, spv::DecorationRestrict);
|
|
b.addDecoration(buffer_container_type, spv::DecorationNonWritable);
|
|
|
|
spv_params.buffer_container = b.createVariable(spv::StorageClassStorageBuffer, buffer_container_type,
|
|
is_vert ? "vertexData" : "fragmentData");
|
|
|
|
b.addDecoration(spv_params.buffer_container, spv::DecorationBinding, is_vert ? 0 : 1);
|
|
|
|
for (auto &[index, buffer] : spv_params.buffers) {
|
|
const std::string member_name = fmt::format("buffer{}", index);
|
|
b.addMemberDecoration(buffer_container_type, buffer.index_in_container, spv::DecorationOffset, buffer.base);
|
|
b.addMemberName(buffer_container_type, buffer.index_in_container, member_name.c_str());
|
|
}
|
|
}
|
|
|
|
for (const auto &buffer : program_input.uniform_buffers) {
|
|
if (buffer.reg_block_size > 0) {
|
|
const uint32_t reg_block_size_in_f32v = (buffer.reg_block_size + 3) / 4;
|
|
const auto spv_buffer = b.createAccessChain(spv::StorageClassStorageBuffer, spv_params.buffer_container,
|
|
{ b.makeIntConstant(spv_params.buffers.at(buffer.index).index_in_container) });
|
|
copy_uniform_block_to_register(b, spv_params.uniforms, spv_buffer, ite_copy, buffer.reg_start_offset, reg_block_size_in_f32v);
|
|
}
|
|
}
|
|
|
|
const auto add_var_to_reg = [&](const Input &input, const std::string &name, std::uint16_t semantic, bool pa, std::int32_t location) {
|
|
const spv::Id param_type = get_param_type(b, input);
|
|
int type_size = get_data_type_size(input.type);
|
|
spv::Id var = b.createVariable(spv::StorageClassInput, param_type, name.c_str());
|
|
|
|
switch (semantic) {
|
|
case SCE_GXM_PARAMETER_SEMANTIC_INDEX:
|
|
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInVertexId);
|
|
break;
|
|
|
|
case SCE_GXM_PARAMETER_SEMANTIC_INSTANCE:
|
|
b.addDecoration(var, spv::DecorationBuiltIn, spv::BuiltInInstanceId);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (location != -1) {
|
|
b.addDecoration(var, spv::DecorationLocation, location);
|
|
}
|
|
|
|
translation_state.interfaces.push_back(var);
|
|
VarToReg var_to_reg;
|
|
var_to_reg.var = var;
|
|
var_to_reg.pa = pa;
|
|
var_to_reg.offset = input.offset;
|
|
var_to_reg.size = input.array_size * input.component_count * 4;
|
|
var_to_reg.dtype = input.type;
|
|
translation_state.var_to_regs.push_back(var_to_reg);
|
|
};
|
|
|
|
for (const auto &sampler : program_input.samplers) {
|
|
const auto sampler_spv_var = create_param_sampler(b, (program.is_vertex() ? "vertTex_" : "fragTex_") + sampler.name, (sampler.is_cube ? spv::DimCube : spv::Dim2D));
|
|
samplers.emplace(sampler.index, sampler_spv_var);
|
|
|
|
// Prefer smaller slot index for fragments since they are gonna be used frequently.
|
|
b.addDecoration(sampler_spv_var, spv::DecorationBinding, sampler.index + (program.is_vertex() ? SCE_GXM_MAX_TEXTURE_UNITS : 0));
|
|
}
|
|
|
|
// Log parameters
|
|
for (size_t i = 0; i < program.parameter_count; ++i) {
|
|
const SceGxmProgramParameter ¶meter = gxp_parameters[i];
|
|
uint16_t curi = parameter.category;
|
|
if (parameter.category == SCE_GXM_PARAMETER_CATEGORY_UNIFORM || parameter.category == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE) {
|
|
bool is_uniform = parameter.category == SCE_GXM_PARAMETER_CATEGORY_UNIFORM;
|
|
std::string var_name = gxp::parameter_name(parameter);
|
|
|
|
auto container = gxp::get_container_by_index(program, parameter.container_index);
|
|
std::uint32_t offset = parameter.resource_index;
|
|
|
|
if (container) {
|
|
offset = container->base_sa_offset + parameter.resource_index;
|
|
}
|
|
|
|
const auto parameter_type = gxp::parameter_type(parameter);
|
|
const auto [store_type, param_type_name] = shader::get_parameter_type_store_and_name(parameter_type);
|
|
std::string param_log = fmt::format("[{} + {}] {}a{} = ({}{}) {}",
|
|
gxp::get_container_name(parameter.container_index), parameter.resource_index,
|
|
is_uniform ? "s" : "p", offset, param_type_name, parameter.component_count, var_name);
|
|
|
|
if (parameter.array_size > 1) {
|
|
param_log += fmt::format("[{}]", parameter.array_size);
|
|
}
|
|
|
|
LOG_DEBUG(param_log);
|
|
}
|
|
}
|
|
|
|
std::int32_t in_fcount_allocated = 0;
|
|
|
|
for (const auto &input : program_input.inputs) {
|
|
std::visit(overloaded{
|
|
[&](const LiteralInputSource &s) {
|
|
literal_pairs.emplace_back(input.offset, b.makeFloatConstant(s.data));
|
|
// Pair sort automatically sort offset for us
|
|
std::sort(literal_pairs.begin(), literal_pairs.end());
|
|
},
|
|
[&](const UniformBufferInputSource &s) {
|
|
Operand reg;
|
|
reg.bank = RegisterBank::SECATTR;
|
|
reg.num = input.offset;
|
|
reg.type = DataType::INT32;
|
|
const auto base = buffer_bases.at(s.index) + s.base;
|
|
utils::store(b, spv_params, utils, features, reg, b.makeIntConstant(base), 0b1, 0);
|
|
},
|
|
[&](const DependentSamplerInputSource &s) {
|
|
const auto spv_sampler = samplers.at(s.index);
|
|
spv_params.samplers.emplace(input.offset, spv_sampler);
|
|
},
|
|
[&](const AttributeInputSource &s) {
|
|
add_var_to_reg(input, s.name, s.semantic, true, in_fcount_allocated / 4);
|
|
in_fcount_allocated += ((input.array_size * input.component_count + 3) / 4 * 4);
|
|
} },
|
|
input.source);
|
|
}
|
|
|
|
if ((in_fcount_allocated == 0) && (program.primary_reg_count != 0)) {
|
|
// Using hint to create attribute. Looks like attribute with F32 types are stripped, otherwise
|
|
// whole shader symbols are kept...
|
|
if (hint_attributes) {
|
|
LOG_INFO("Shader stripped all symbols, trying to use hint attributes");
|
|
|
|
for (std::size_t i = 0; i < hint_attributes->size(); i++) {
|
|
Input inp;
|
|
inp.offset = hint_attributes->at(i).regIndex;
|
|
inp.bank = RegisterBank::PRIMATTR;
|
|
inp.generic_type = GenericType::VECTOR;
|
|
inp.type = DataType::F32;
|
|
inp.component_count = 4;
|
|
inp.array_size = (hint_attributes->at(i).componentCount + 3) >> 2;
|
|
|
|
add_var_to_reg(inp, fmt::format("attribute{}", i), 0, true, static_cast<std::int32_t>(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
// We should avoid ugly and long GLSL code generated. Also, inefficient SPIR-V code.
|
|
// Packing literals into vector may help solving this.
|
|
if (literal_pairs.size() != 0) {
|
|
std::uint32_t composite_base = literal_pairs[0].first;
|
|
|
|
std::vector<spv::Id> constituents;
|
|
constituents.push_back(literal_pairs[0].second);
|
|
|
|
auto create_new_literal_pack = [&]() {
|
|
// Create new literal composite
|
|
spv::Id composite_var;
|
|
if (constituents.size() > 1) {
|
|
composite_var = b.makeCompositeConstant(b.makeVectorType(f32_type, static_cast<int>(constituents.size())),
|
|
constituents);
|
|
} else {
|
|
composite_var = constituents[0];
|
|
}
|
|
create_input_variable(b, spv_params, utils, features, nullptr, RegisterBank::SECATTR, composite_base, spv::NoResult,
|
|
static_cast<int>(constituents.size() * 4), composite_var);
|
|
};
|
|
|
|
for (std::uint32_t i = 1; i < program.literals_count; i++) {
|
|
// Detect sequence literals.
|
|
if (literal_pairs[i].first == composite_base + constituents.size() && constituents.size() < 4) {
|
|
constituents.push_back(literal_pairs[i].second);
|
|
} else {
|
|
// The sequence ended. Create new literal pack.
|
|
create_new_literal_pack();
|
|
|
|
// Reset and set new base
|
|
composite_base = literal_pairs[i].first;
|
|
constituents.clear();
|
|
constituents.push_back(literal_pairs[i].second);
|
|
}
|
|
}
|
|
|
|
// Is there any constituents left ? We should create a literal pack if there is.
|
|
if (!constituents.empty()) {
|
|
create_new_literal_pack();
|
|
}
|
|
}
|
|
|
|
spv::Id f32 = b.makeFloatType(32);
|
|
spv::Id v4 = b.makeVectorType(f32, 4);
|
|
|
|
if (program_type == SceGxmProgramType::Vertex) {
|
|
// Create the default reg uniform buffer
|
|
spv::Id render_buf_type = b.makeStructType({ v4, f32, f32, f32 }, "GxmRenderVertBufferBlock");
|
|
b.addDecoration(render_buf_type, spv::DecorationBlock);
|
|
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
|
|
|
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, 0);
|
|
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, 16);
|
|
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, 20);
|
|
b.addMemberDecoration(render_buf_type, 3, spv::DecorationOffset, 24);
|
|
|
|
b.addMemberName(render_buf_type, 0, "viewport_flip");
|
|
b.addMemberName(render_buf_type, 1, "viewport_flag");
|
|
b.addMemberName(render_buf_type, 2, "screen_width");
|
|
b.addMemberName(render_buf_type, 3, "screen_height");
|
|
|
|
translation_state.render_info_id = b.createVariable(spv::StorageClassUniform, render_buf_type, "renderVertInfo");
|
|
|
|
b.addDecoration(translation_state.render_info_id, spv::DecorationBinding, 2);
|
|
}
|
|
|
|
if (program_type == SceGxmProgramType::Fragment) {
|
|
spv::Id render_buf_type = b.makeStructType({ f32, f32, f32 }, "GxmRenderFragBufferBlock");
|
|
|
|
b.addDecoration(render_buf_type, spv::DecorationBlock);
|
|
b.addDecoration(render_buf_type, spv::DecorationGLSLShared);
|
|
|
|
b.addMemberDecoration(render_buf_type, 0, spv::DecorationOffset, 0);
|
|
b.addMemberDecoration(render_buf_type, 1, spv::DecorationOffset, 4);
|
|
b.addMemberDecoration(render_buf_type, 2, spv::DecorationOffset, 8);
|
|
|
|
b.addMemberName(render_buf_type, 0, "back_disabled");
|
|
b.addMemberName(render_buf_type, 1, "front_disabled");
|
|
b.addMemberName(render_buf_type, 2, "writing_mask");
|
|
|
|
translation_state.render_info_id = b.createVariable(spv::StorageClassUniform, render_buf_type, "renderFragInfo");
|
|
|
|
b.addDecoration(translation_state.render_info_id, spv::DecorationBinding, 3);
|
|
|
|
create_fragment_inputs(b, spv_params, utils, features, translation_state, texture_queries, samplers, program);
|
|
}
|
|
|
|
return spv_params;
|
|
}
|
|
|
|
static void generate_shader_body(spv::Builder &b, const SpirvShaderParameters ¶meters, const SceGxmProgram &program,
|
|
const FeatureState &features, utils::SpirvUtilFunctions &utils, spv::Function *begin_hook_func, spv::Function *end_hook_func, const NonDependentTextureQueryCallInfos &texture_queries) {
|
|
// Do texture queries
|
|
usse::convert_gxp_usse_to_spirv(b, program, features, parameters, utils, begin_hook_func, end_hook_func, texture_queries);
|
|
}
|
|
|
|
static spv::Function *make_frag_finalize_function(spv::Builder &b, const SpirvShaderParameters ¶meters,
|
|
const SceGxmProgram &program, utils::SpirvUtilFunctions &utils, const FeatureState &features, TranslationState &translate_state) {
|
|
std::vector<std::vector<spv::Decoration>> decorations;
|
|
|
|
spv::Block *frag_fin_block;
|
|
spv::Block *last_build_point = b.getBuildPoint();
|
|
|
|
spv::Function *frag_fin_func = b.makeFunctionEntry(spv::NoPrecision, b.makeVoidType(), "frag_output_finalize", {},
|
|
decorations, &frag_fin_block);
|
|
|
|
const SceGxmParameterType param_type = program.get_fragment_output_type();
|
|
|
|
Operand color_val_operand;
|
|
color_val_operand.bank = program.is_native_color() ? RegisterBank::OUTPUT : RegisterBank::PRIMATTR;
|
|
color_val_operand.num = 0;
|
|
color_val_operand.swizzle = SWIZZLE_CHANNEL_4_DEFAULT;
|
|
color_val_operand.type = std::get<0>(shader::get_parameter_type_store_and_name(param_type));
|
|
|
|
auto vertex_varyings_ptr = program.vertex_varyings();
|
|
|
|
int reg_off = 0;
|
|
if (!program.is_native_color() && vertex_varyings_ptr->output_param_type == 1) {
|
|
reg_off = vertex_varyings_ptr->fragment_output_start;
|
|
if (reg_off != 0) {
|
|
LOG_INFO("Non zero pa offset: {} at {}", reg_off, translate_state.hash.c_str());
|
|
}
|
|
}
|
|
|
|
spv::Id color = utils::load(b, parameters, utils, features, color_val_operand, 0xF, reg_off);
|
|
if (!is_float_data_type(color_val_operand.type)) {
|
|
color = utils::convert_to_float(b, color, color_val_operand.type, true);
|
|
}
|
|
|
|
if (program.is_native_color() && features.should_use_shader_interlock() && !translate_state.should_gl_spirv_compatible) {
|
|
spv::Id signed_i32 = b.makeIntegerType(32, true);
|
|
spv::Id coord_id = b.createLoad(translate_state.frag_coord_id);
|
|
spv::Id depth = b.createOp(spv::OpAccessChain, b.makeFloatType(32), { coord_id, b.makeIntConstant(2) });
|
|
spv::Id translated_id = b.createUnaryOp(spv::OpConvertFToS, b.makeVectorType(signed_i32, 4), coord_id);
|
|
translated_id = b.createOp(spv::OpVectorShuffle, b.makeVectorType(signed_i32, 2), { translated_id, translated_id, 0, 1 });
|
|
b.createNoResultOp(spv::OpImageWrite, { b.createLoad(translate_state.color_attachment_id), translated_id, color });
|
|
} else {
|
|
spv::Id out = b.createVariable(spv::StorageClassOutput, b.makeVectorType(b.makeFloatType(32), 4), "out_color");
|
|
translate_state.interfaces.push_back(out);
|
|
b.addDecoration(out, spv::DecorationLocation, 0);
|
|
b.createStore(color, out);
|
|
}
|
|
|
|
// Discard masked fragments
|
|
spv::Id current_coord = translate_state.frag_coord_id;
|
|
spv::Id i32 = b.makeIntegerType(32, true);
|
|
current_coord = b.createUnaryOp(spv::OpConvertFToS, b.makeVectorType(i32, 4), b.createLoad(current_coord));
|
|
current_coord = b.createOp(spv::OpVectorShuffle, b.makeVectorType(i32, 2), { current_coord, current_coord, 0, 1 });
|
|
spv::Id sampled_type = b.makeFloatType(32);
|
|
spv::Id v4 = b.makeVectorType(sampled_type, 4);
|
|
spv::Id texel = b.createOp(spv::OpImageRead, v4, { b.createLoad(translate_state.mask_id), current_coord });
|
|
spv::Id rezero = b.makeFloatConstant(0.5f);
|
|
spv::Id zero = b.makeCompositeConstant(v4, { rezero, rezero, rezero, rezero });
|
|
spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeBoolType(), { texel, zero });
|
|
spv::Id pred2 = b.createUnaryOp(spv::OpAll, b.makeBoolType(), pred);
|
|
spv::Builder::If cond_builder(pred2, spv::SelectionControlMaskNone, b);
|
|
b.makeDiscard();
|
|
cond_builder.makeEndIf();
|
|
|
|
b.makeReturn(false);
|
|
b.setBuildPoint(last_build_point);
|
|
|
|
return frag_fin_func;
|
|
}
|
|
|
|
static spv::Function *make_vert_finalize_function(spv::Builder &b, const SpirvShaderParameters ¶meters,
|
|
const SceGxmProgram &program, utils::SpirvUtilFunctions &utils, const FeatureState &features, TranslationState &translation_state) {
|
|
std::vector<std::vector<spv::Decoration>> decorations;
|
|
|
|
spv::Block *vert_fin_block;
|
|
spv::Block *last_build_point = b.getBuildPoint();
|
|
|
|
spv::Function *vert_fin_func = b.makeFunctionEntry(spv::NoPrecision, b.makeVoidType(), "vert_output_finalize", {},
|
|
decorations, &vert_fin_block);
|
|
|
|
gxp::GxmVertexOutputTexCoordInfos coord_infos = {};
|
|
SceGxmVertexProgramOutputs vertex_outputs = gxp::get_vertex_outputs(program, &coord_infos);
|
|
|
|
static const auto calculate_copy_comp_count = [](uint8_t info) {
|
|
// TexCoord info uses preset values described below for determining lengths.
|
|
uint8_t length = 0;
|
|
if (info & 0b001u)
|
|
length += 2; // uses xy
|
|
if (info & 0b010u)
|
|
length += 1; // uses z
|
|
if (info & 0b100u)
|
|
length += 1; // uses w
|
|
|
|
return length;
|
|
};
|
|
|
|
VertexProgramOutputPropertiesMap vertex_properties_map;
|
|
// list is used here to gurantee the vertex outputs are written in right order
|
|
std::list<SceGxmVertexProgramOutputs> vertex_outputs_list;
|
|
const auto add_vertex_output_info = [&](SceGxmVertexProgramOutputs vo, const char *name, std::uint32_t component_count, std::uint32_t location) {
|
|
vertex_properties_map.emplace(vo, VertexProgramOutputProperties(name, component_count, location));
|
|
vertex_outputs_list.push_back(vo);
|
|
};
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_POSITION, "v_Position", 4, 0);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_COLOR0, "v_Color0", 4, 1);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_COLOR1, "v_Color1", 4, 2);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_FOG, "v_Fog", 2, 3);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD0, "v_TexCoord0", calculate_copy_comp_count(coord_infos[0]), 4);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD1, "v_TexCoord1", calculate_copy_comp_count(coord_infos[1]), 5);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD2, "v_TexCoord2", calculate_copy_comp_count(coord_infos[2]), 6);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD3, "v_TexCoord3", calculate_copy_comp_count(coord_infos[3]), 7);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD4, "v_TexCoord4", calculate_copy_comp_count(coord_infos[4]), 8);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD5, "v_TexCoord5", calculate_copy_comp_count(coord_infos[5]), 9);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD6, "v_TexCoord6", calculate_copy_comp_count(coord_infos[6]), 10);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD7, "v_TexCoord7", calculate_copy_comp_count(coord_infos[7]), 11);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD8, "v_TexCoord8", calculate_copy_comp_count(coord_infos[8]), 12);
|
|
add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_TEXCOORD9, "v_TexCoord9", calculate_copy_comp_count(coord_infos[9]), 13);
|
|
// TODO: this should be translated to gl_PointSize
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_PSIZE, "v_Psize", 1);
|
|
// TODO: these should be translated to gl_ClipDistance
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP0, "v_Clip0", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP1, "v_Clip1", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP2, "v_Clip2", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP3, "v_Clip3", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP4, "v_Clip4", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP5, "v_Clip5", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP6, "v_Clip6", 1);
|
|
// add_vertex_output_info(SCE_GXM_VERTEX_PROGRAM_OUTPUT_CLIP7, "v_Clip7", 1);
|
|
|
|
Operand o_op;
|
|
o_op.bank = RegisterBank::OUTPUT;
|
|
o_op.num = 0;
|
|
o_op.swizzle = SWIZZLE_CHANNEL_4_DEFAULT;
|
|
|
|
for (const auto vo : vertex_outputs_list) {
|
|
if (vertex_outputs & vo) {
|
|
const auto vo_typed = static_cast<SceGxmVertexProgramOutputs>(vo);
|
|
VertexProgramOutputProperties properties = vertex_properties_map.at(vo_typed);
|
|
|
|
// TODO: use the actual size of variable
|
|
const spv::Id out_type = b.makeVectorType(b.makeFloatType(32), 4);
|
|
const spv::Id out_var = b.createVariable(spv::StorageClassOutput, out_type, properties.name.c_str());
|
|
|
|
b.addDecoration(out_var, spv::DecorationLocation, properties.location);
|
|
translation_state.interfaces.push_back(out_var);
|
|
|
|
// Do store
|
|
spv::Id o_val = utils::load(b, parameters, utils, features, o_op, 0b1111, 0);
|
|
|
|
if (vo == SCE_GXM_VERTEX_PROGRAM_OUTPUT_POSITION) {
|
|
b.addDecoration(out_var, spv::DecorationBuiltIn, spv::BuiltInPosition);
|
|
|
|
// Transform screen space coordinate to ndc when viewport is disabled.
|
|
spv::Id f32 = b.makeFloatType(32);
|
|
spv::Id v4 = b.makeVectorType(b.makeFloatType(32), 4);
|
|
spv::Id half = b.makeFloatConstant(0.5f);
|
|
spv::Id one = b.makeFloatConstant(1.0f);
|
|
spv::Id neg_one = b.makeFloatConstant(-1.0f);
|
|
spv::Id two = b.makeFloatConstant(2.0f);
|
|
spv::Id neg_two = b.makeFloatConstant(-2.0f);
|
|
spv::Id zero = b.makeFloatConstant(0.0f);
|
|
|
|
spv::Id viewport_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(1) });
|
|
spv::Id screen_width_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(2) });
|
|
spv::Id screen_height_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(3) });
|
|
|
|
spv::Id pred = b.createOp(spv::OpFOrdLessThan, b.makeBoolType(), { b.createLoad(viewport_id), half });
|
|
spv::Builder::If cond_builder(pred, spv::SelectionControlMaskNone, b);
|
|
|
|
spv::Id width_recp = b.createBinOp(spv::OpFDiv, f32, two, b.createLoad(screen_width_id));
|
|
spv::Id height_recp = b.createBinOp(spv::OpFDiv, f32, neg_two, b.createLoad(screen_height_id));
|
|
spv::Id scale = b.createCompositeConstruct(v4, { width_recp, height_recp, one, one });
|
|
spv::Id constant = b.createCompositeConstruct(v4, { neg_one, one, zero, zero });
|
|
spv::Id o_val2 = b.createBinOp(spv::OpFMul, v4, o_val, scale);
|
|
o_val2 = b.createBinOp(spv::OpFAdd, v4, o_val2, constant);
|
|
|
|
if (translation_state.render_info_id != spv::NoResult) {
|
|
spv::Id flip_vec_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(0) });
|
|
o_val2 = b.createBinOp(spv::OpFMul, v4, o_val2, flip_vec_id);
|
|
}
|
|
|
|
// o_val2 = (x,y) * (2/width, -2/height) + (-1,1)
|
|
b.createStore(o_val2, out_var);
|
|
|
|
// Note: Depth range and user clip planes are ineffective in this mode
|
|
// However, that can't be directly translated, so we just gonna set it to w here
|
|
spv::Id z_ref = b.createAccessChain(spv::StorageClassOutput, out_var, { b.makeIntConstant(2) });
|
|
spv::Id w_ref = b.createAccessChain(spv::StorageClassOutput, out_var, { b.makeIntConstant(3) });
|
|
|
|
b.createStore(b.createLoad(w_ref), z_ref);
|
|
|
|
cond_builder.makeBeginElse();
|
|
|
|
if (translation_state.render_info_id != spv::NoResult) {
|
|
spv::Id flip_vec_id = b.createAccessChain(spv::StorageClassUniform, translation_state.render_info_id, { b.makeIntConstant(0) });
|
|
o_val = b.createBinOp(spv::OpFMul, out_type, o_val, flip_vec_id);
|
|
}
|
|
|
|
b.createStore(o_val, out_var);
|
|
cond_builder.makeEndIf();
|
|
} else {
|
|
b.createStore(o_val, out_var);
|
|
}
|
|
|
|
o_op.num += properties.component_count;
|
|
}
|
|
}
|
|
|
|
b.makeReturn(false);
|
|
b.setBuildPoint(last_build_point);
|
|
|
|
return vert_fin_func;
|
|
}
|
|
|
|
static spv::Function *make_frag_initialize_function(spv::Builder &b, TranslationState &translate_state) {
|
|
std::vector<std::vector<spv::Decoration>> decorations;
|
|
|
|
spv::Block *frag_init_block;
|
|
spv::Block *last_build_point = b.getBuildPoint();
|
|
|
|
spv::Function *frag_init_func = b.makeFunctionEntry(spv::NoPrecision, b.makeVoidType(), "frag_init", {},
|
|
decorations, &frag_init_block);
|
|
|
|
// Note! We use CCW as Front face, however we invert the coordinates so the front face is actually CW, identical to GXM (GXM also has front-face as CW)
|
|
spv::Id booltype = b.makeBoolType();
|
|
spv::Id zero = b.makeFloatConstant(0.0f);
|
|
|
|
spv::Id front_facing = b.createVariable(spv::StorageClassInput, booltype, "gl_FrontFacing");
|
|
spv::Id front_disabled = b.createAccessChain(spv::StorageClassUniform, translate_state.render_info_id, { b.makeIntConstant(1) });
|
|
spv::Id back_disabled = b.createAccessChain(spv::StorageClassUniform, translate_state.render_info_id, { b.makeIntConstant(0) });
|
|
b.addDecoration(front_facing, spv::DecorationBuiltIn, spv::BuiltInFrontFacing);
|
|
|
|
front_facing = b.createLoad(front_facing);
|
|
|
|
spv::Id pred = b.createOp(spv::OpLogicalAnd, booltype, { b.createBinOp(spv::OpFOrdNotEqual, booltype, b.createLoad(front_disabled), zero), front_facing });
|
|
|
|
spv::Builder::If front_disabled_cond_builder(pred, spv::SelectionControlMaskNone, b);
|
|
b.makeDiscard();
|
|
front_disabled_cond_builder.makeEndIf();
|
|
|
|
pred = b.createOp(spv::OpLogicalAnd, booltype, { b.createBinOp(spv::OpFOrdNotEqual, booltype, b.createLoad(back_disabled), zero), b.createUnaryOp(spv::OpLogicalNot, booltype, front_facing) });
|
|
|
|
spv::Builder::If back_disabled_cond_builder(pred, spv::SelectionControlMaskNone, b);
|
|
b.makeDiscard();
|
|
back_disabled_cond_builder.makeEndIf();
|
|
|
|
b.makeReturn(false);
|
|
b.setBuildPoint(last_build_point);
|
|
|
|
return frag_init_func;
|
|
}
|
|
|
|
static void generate_update_mask_body(spv::Builder &b, utils::SpirvUtilFunctions &utils, const FeatureState &features, TranslationState &translate_state) {
|
|
const spv::Id writing_mask_var = b.createAccessChain(spv::StorageClassUniform, translate_state.render_info_id, { b.makeIntConstant(2) });
|
|
const spv::Id writing_mask = b.createLoad(writing_mask_var);
|
|
|
|
const spv::Id v4 = b.makeVectorType(b.makeFloatType(32), 4);
|
|
const spv::Id mask_v = b.createCompositeConstruct(v4, { writing_mask, writing_mask, writing_mask, writing_mask });
|
|
|
|
const spv::Id out = b.createVariable(spv::StorageClassOutput, v4, "out_color");
|
|
translate_state.interfaces.push_back(out);
|
|
b.addDecoration(out, spv::DecorationLocation, 0);
|
|
|
|
b.createStore(mask_v, out);
|
|
}
|
|
|
|
static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const std::string &shader_hash, const FeatureState &features, TranslationState &translation_state, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
|
SpirvCode spirv;
|
|
|
|
SceGxmProgramType program_type = program.get_type();
|
|
|
|
spv::SpvBuildLogger spv_logger;
|
|
spv::Builder b(SPV_VERSION, 0x1337 << 12, &spv_logger);
|
|
b.setSourceFile(shader_hash);
|
|
b.setEmitOpLines();
|
|
b.addSourceExtension("gxp");
|
|
b.setMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
|
|
|
|
// Capabilities
|
|
b.addCapability(spv::Capability::CapabilityShader);
|
|
b.addCapability(spv::Capability::CapabilityFloat16);
|
|
|
|
NonDependentTextureQueryCallInfos texture_queries;
|
|
utils::SpirvUtilFunctions utils;
|
|
|
|
std::string entry_point_name;
|
|
spv::ExecutionModel execution_model;
|
|
|
|
translation_state.hash = shader_hash;
|
|
|
|
switch (program_type) {
|
|
default:
|
|
LOG_ERROR("Unknown GXM program type");
|
|
[[fallthrough]];
|
|
// fallthrough
|
|
case Vertex:
|
|
entry_point_name = "main_vs";
|
|
execution_model = spv::ExecutionModelVertex;
|
|
break;
|
|
case Fragment:
|
|
entry_point_name = "main_fs";
|
|
execution_model = spv::ExecutionModelFragment;
|
|
break;
|
|
}
|
|
|
|
std::string disasm_dump;
|
|
|
|
// Put disasm storage
|
|
disasm::disasm_storage = &disasm_dump;
|
|
|
|
// Entry point
|
|
spv::Function *spv_func_main = b.makeEntryPoint(entry_point_name.c_str());
|
|
spv::Function *end_hook_func = nullptr;
|
|
spv::Function *begin_hook_func = nullptr;
|
|
|
|
std::vector<spv::Id> empty_args;
|
|
|
|
// Lock/unlock and read texel for shader interlock. Texture barrier will have glTextureBarrier() called so we don't
|
|
// have to worry too much. Texture barrier will not be accurate and may be broken though.
|
|
if (program_type == SceGxmProgramType::Fragment) {
|
|
b.addExecutionMode(spv_func_main, spv::ExecutionModeOriginLowerLeft);
|
|
|
|
if (program.is_native_color() && features.should_use_shader_interlock()) {
|
|
b.addExecutionMode(spv_func_main, spv::ExecutionModePixelInterlockOrderedEXT);
|
|
b.addExtension("SPV_EXT_fragment_shader_interlock");
|
|
b.addCapability(spv::CapabilityFragmentShaderPixelInterlockEXT);
|
|
|
|
b.createNoResultOp(spv::OpBeginInvocationInterlockEXT);
|
|
}
|
|
}
|
|
|
|
// Generate parameters
|
|
SpirvShaderParameters parameters = create_parameters(b, program, utils, features, translation_state, program_type, texture_queries, hint_attributes);
|
|
|
|
if (!translation_state.is_maskupdate) {
|
|
if (program.is_fragment()) {
|
|
begin_hook_func = make_frag_initialize_function(b, translation_state);
|
|
end_hook_func = make_frag_finalize_function(b, parameters, program, utils, features, translation_state);
|
|
} else {
|
|
end_hook_func = make_vert_finalize_function(b, parameters, program, utils, features, translation_state);
|
|
}
|
|
|
|
for (auto &var_to_reg : translation_state.var_to_regs) {
|
|
create_input_variable(b, parameters, utils, features, "", var_to_reg.pa ? RegisterBank::PRIMATTR : RegisterBank::SECATTR,
|
|
var_to_reg.offset, spv::NoResult, var_to_reg.size, var_to_reg.var, var_to_reg.dtype);
|
|
}
|
|
|
|
// Initialize vertex output to 0
|
|
if (program.is_vertex()) {
|
|
spv::Id i32_type = b.makeIntType(32);
|
|
spv::Id ite = b.createVariable(spv::StorageClassFunction, i32_type, "i");
|
|
spv::Id v4 = b.makeVectorType(b.makeFloatType(32), 4);
|
|
spv::Id rezero = b.makeFloatConstant(0.0f);
|
|
spv::Id rezero_v = b.makeCompositeConstant(v4, { rezero, rezero, rezero, rezero });
|
|
utils::make_for_loop(b, ite, b.makeIntConstant(0), b.makeIntConstant(REG_O_COUNT / 4), [&]() {
|
|
Operand target_to_store;
|
|
spv::Id dest = b.createAccessChain(spv::StorageClassPrivate, parameters.outs, { b.createLoad(ite) });
|
|
b.createStore(rezero_v, dest);
|
|
});
|
|
}
|
|
|
|
generate_shader_body(b, parameters, program, features, utils, begin_hook_func, end_hook_func, texture_queries);
|
|
} else {
|
|
generate_update_mask_body(b, utils, features, translation_state);
|
|
}
|
|
b.leaveFunction();
|
|
|
|
// Add entry point to Builder
|
|
auto entrypoint = b.addEntryPoint(execution_model, spv_func_main, entry_point_name.c_str());
|
|
for (auto &i : translation_state.interfaces) {
|
|
entrypoint->addIdOperand(i);
|
|
}
|
|
|
|
auto spirv_log = spv_logger.getAllMessages();
|
|
if (!spirv_log.empty())
|
|
LOG_ERROR("SPIR-V Error:\n{}", spirv_log);
|
|
|
|
if (dumper) {
|
|
dumper("dsm", disasm_dump);
|
|
}
|
|
|
|
b.dump(spirv);
|
|
|
|
if (LOG_SHADER_CODE || force_shader_debug) {
|
|
std::string spirv_dump;
|
|
spirv_disasm_print(spirv, &spirv_dump);
|
|
if (dumper) {
|
|
dumper("spv", spirv_dump);
|
|
}
|
|
}
|
|
|
|
if (DUMP_SPIRV_BINARIES) {
|
|
// TODO: use base path host var
|
|
std::ofstream spirv_dump(shader_hash + ".spv", std::ios::binary);
|
|
spirv_dump.write((char *)&spirv, spirv.size() * sizeof(uint32_t));
|
|
spirv_dump.close();
|
|
}
|
|
|
|
return spirv;
|
|
}
|
|
|
|
static std::string convert_spirv_to_glsl(const std::string &shader_name, SpirvCode spirv_binary, const FeatureState &features, TranslationState &translation_state, bool is_native_color) {
|
|
spirv_cross::CompilerGLSL glsl(std::move(spirv_binary));
|
|
|
|
spirv_cross::CompilerGLSL::Options options;
|
|
|
|
options.version = 430;
|
|
options.es = false;
|
|
options.enable_420pack_extension = true;
|
|
|
|
// TODO: this might be needed in the future
|
|
// options.vertex.flip_vert_y = true;
|
|
|
|
glsl.set_common_options(options);
|
|
glsl.add_header_line("// Shader Name: " + shader_name);
|
|
|
|
if (features.direct_fragcolor && translation_state.last_frag_data_id != spv::NoResult) {
|
|
glsl.require_extension("GL_EXT_shader_framebuffer_fetch");
|
|
|
|
// Do not generate declaration for gl_LastFragData
|
|
glsl.set_remapped_variable_state(translation_state.last_frag_data_id, true);
|
|
glsl.set_name(translation_state.last_frag_data_id, "gl_LastFragData");
|
|
}
|
|
|
|
if (translation_state.frag_coord_id != spv::NoResult) {
|
|
glsl.set_remapped_variable_state(translation_state.frag_coord_id, true);
|
|
glsl.set_name(translation_state.frag_coord_id, "gl_FragCoord");
|
|
}
|
|
if (features.support_shader_interlock) {
|
|
if (translation_state.is_fragment && is_native_color) {
|
|
glsl.add_header_line("layout(early_fragment_tests) in;\n");
|
|
}
|
|
glsl.require_extension("GL_ARB_fragment_shader_interlock");
|
|
}
|
|
// Compile to GLSL, ready to give to GL driver.
|
|
std::string source = glsl.compile();
|
|
return source;
|
|
}
|
|
|
|
// ***********************
|
|
// * Functions (utility) *
|
|
// ***********************
|
|
|
|
void spirv_disasm_print(const usse::SpirvCode &spirv_binary, std::string *spirv_dump) {
|
|
std::stringstream spirv_disasm;
|
|
spv::Disassemble(spirv_disasm, spirv_binary);
|
|
|
|
if (spirv_dump) {
|
|
*spirv_dump = spirv_disasm.str();
|
|
}
|
|
|
|
LOG_DEBUG("SPIR-V Disassembly:\n{}", spirv_dump ? *spirv_dump : spirv_disasm.str());
|
|
}
|
|
|
|
// ***************************
|
|
// * Functions (exposed API) *
|
|
// ***************************
|
|
usse::SpirvCode convert_gxp_to_spirv(const SceGxmProgram &program, const std::string &shader_name, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
|
TranslationState translation_state;
|
|
translation_state.is_fragment = program.is_fragment();
|
|
translation_state.is_maskupdate = maskupdate;
|
|
translation_state.should_gl_spirv_compatible = true;
|
|
|
|
return convert_gxp_to_spirv_impl(program, shader_name, features, translation_state, hint_attributes, force_shader_debug, dumper);
|
|
}
|
|
|
|
std::string convert_gxp_to_glsl(const SceGxmProgram &program, const std::string &shader_name, const FeatureState &features, const std::vector<SceGxmVertexAttribute> *hint_attributes, bool maskupdate, bool force_shader_debug, std::function<bool(const std::string &ext, const std::string &dump)> dumper) {
|
|
TranslationState translation_state;
|
|
translation_state.is_fragment = program.is_fragment();
|
|
translation_state.is_maskupdate = maskupdate;
|
|
translation_state.should_gl_spirv_compatible = false;
|
|
|
|
std::vector<uint32_t> spirv_binary = convert_gxp_to_spirv_impl(program, shader_name, features, translation_state, hint_attributes, force_shader_debug, dumper);
|
|
|
|
const auto source = convert_spirv_to_glsl(shader_name, spirv_binary, features, translation_state, program.is_native_color());
|
|
|
|
if (LOG_SHADER_CODE || force_shader_debug) {
|
|
LOG_DEBUG("Generated GLSL:\n{}", source);
|
|
}
|
|
|
|
if (dumper) {
|
|
if (program.is_fragment()) {
|
|
dumper("frag", source);
|
|
} else {
|
|
dumper("vert", source);
|
|
}
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
void convert_gxp_to_glsl_from_filepath(const std::string &shader_filepath) {
|
|
const fs::path shader_filepath_str{ shader_filepath };
|
|
fs::ifstream gxp_stream(shader_filepath_str, fs::ifstream::binary);
|
|
|
|
if (!gxp_stream.is_open())
|
|
return;
|
|
|
|
const auto gxp_file_size = fs::file_size(shader_filepath_str);
|
|
const auto gxp_program = static_cast<SceGxmProgram *>(calloc(gxp_file_size, 1));
|
|
|
|
gxp_stream.read(reinterpret_cast<char *>(gxp_program), gxp_file_size);
|
|
|
|
FeatureState features;
|
|
features.direct_fragcolor = false;
|
|
features.support_shader_interlock = true;
|
|
|
|
convert_gxp_to_glsl(*gxp_program, shader_filepath_str.filename().string(), features, nullptr, false, true);
|
|
|
|
free(gxp_program);
|
|
}
|
|
|
|
} // namespace shader
|