mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
strlcpy() begins with strlen() of its source. It lives in another translation unit, so with no LTO that scan happens at runtime even when the source is a literal whose length the compiler already measured, and the call itself cannot be folded away either. strlcpy_lit() passes the length over as sizeof(). When the destination size is also a constant -- sizeof(buf), the usual case -- nothing is left but the stores memcpy() compiles to: strlcpy "core_options" sizeof(buf) 7.01 ns strlcpy_lit "core_options" sizeof(buf) 0.42 ns strlcpy 41-char literal sizeof(buf) 7.74 ns strlcpy_lit 41-char literal sizeof(buf) 0.71 ns strlcpy "core_options" runtime len 7.11 ns strlcpy_lit "core_options" runtime len 0.70 ns The destination bound is kept. When the literal does not fit it defers to strlcpy(), so the copy truncates as before rather than overrunning; a form that took only the literal would have turned every one of these sites into an overflow waiting for a small buffer. The literal is concatenated with "" so a non-literal source is a compile error rather than a sizeof() that silently measures a pointer. 818 sites across 104 files were converted by parsing arguments rather than by pattern-matching text: only a three-argument call whose second argument is a bare string literal is rewritten, so a macro that happens to expand to a literal is left alone. Line endings are preserved, so the CRLF files carry only their own changed lines. test/compat/test_strl.c compares the two against each other over every destination size from 0 past the literal's length, for literals that are empty, single-character, path-shaped, adjacently concatenated and escape-bearing, checking the return, the terminator and the whole buffer so an overrun past the terminator is caught. ASan+UBSan+LSan and TSan both 4/4. The full menu_displaylist_build_list walk over every displaylist type is byte-identical to before the change (md5 a2a081bcac09cdc8d72d67efd14ac40e).
1921 lines
63 KiB
C++
1921 lines
63 KiB
C++
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2017 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2014-2018 - Ali Bouhlel
|
|
*
|
|
* RetroArch 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 Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch 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 RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <spirv_glsl.hpp>
|
|
#include <spirv_hlsl.hpp>
|
|
#include <spirv_msl.hpp>
|
|
#include <compat/strl.h>
|
|
#include <retro_miscellaneous.h>
|
|
#include <string>
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
|
|
#include "glslang_util.h"
|
|
#if defined(HAVE_GLSLANG)
|
|
#include "glslang.hpp"
|
|
#include "slang_cache.h"
|
|
#endif
|
|
#include "spirv_cross.hpp"
|
|
#include "slang_process.h"
|
|
|
|
#include "../../verbosity.h"
|
|
|
|
static const char *texture_semantic_names[] = {
|
|
"Original",
|
|
"Source",
|
|
"OriginalHistory",
|
|
"PassOutput",
|
|
"PassFeedback",
|
|
"User",
|
|
NULL
|
|
};
|
|
|
|
static const char *texture_semantic_uniform_names[] = {
|
|
"OriginalSize",
|
|
"SourceSize",
|
|
"OriginalHistorySize",
|
|
"PassOutputSize",
|
|
"PassFeedbackSize",
|
|
"UserSize",
|
|
NULL
|
|
};
|
|
|
|
static const char *semantic_uniform_names[] = {
|
|
"MVP",
|
|
"OutputSize",
|
|
"FinalViewportSize",
|
|
"FrameCount",
|
|
"FrameDirection",
|
|
"FrameTimeDelta",
|
|
"OriginalFPS",
|
|
"Rotation",
|
|
"OriginalAspect",
|
|
"OriginalAspectRotated",
|
|
"TotalSubFrames",
|
|
"CurrentSubFrame",
|
|
"HDRMode",
|
|
"BrightnessNits",
|
|
"Scanlines",
|
|
"SubpixelLayout",
|
|
"ExpandGamut",
|
|
"InverseTonemap",
|
|
"HDR10",
|
|
"Gyroscope",
|
|
"Accelerometer",
|
|
"AccelerometerRest"
|
|
};
|
|
|
|
template <typename M, typename S>
|
|
static const char *get_semantic_name(
|
|
const std::unordered_map<std::string, M>* map,
|
|
S semantic, unsigned index)
|
|
{
|
|
for (typename std::unordered_map<std::string, M>::const_iterator it = map->begin();
|
|
it != map->end(); ++it)
|
|
{
|
|
if (it->second.semantic == semantic && it->second.index == index)
|
|
return it->first.c_str();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static slang_texture_semantic slang_name_to_texture_semantic(
|
|
const std::unordered_map<std::string, slang_texture_semantic_map> &semantic_map,
|
|
const std::string &name, unsigned *index)
|
|
{
|
|
std::unordered_map<std::string, slang_texture_semantic_map>::const_iterator itr =
|
|
semantic_map.find(name);
|
|
if (itr != semantic_map.end())
|
|
{
|
|
*index = itr->second.index;
|
|
return itr->second.semantic;
|
|
}
|
|
|
|
return slang_name_to_texture_semantic_array(
|
|
name.c_str(), texture_semantic_names, index);
|
|
}
|
|
|
|
static slang_texture_semantic slang_uniform_name_to_texture_semantic(
|
|
const std::unordered_map<std::string, slang_texture_semantic_map> &semantic_map,
|
|
const std::string &name, unsigned *index)
|
|
{
|
|
std::unordered_map<std::string, slang_texture_semantic_map>::const_iterator itr =
|
|
semantic_map.find(name);
|
|
if (itr != semantic_map.end())
|
|
{
|
|
*index = itr->second.index;
|
|
return itr->second.semantic;
|
|
}
|
|
|
|
return slang_name_to_texture_semantic_array(name.c_str(),
|
|
texture_semantic_uniform_names, index);
|
|
}
|
|
|
|
static slang_semantic slang_uniform_name_to_semantic(
|
|
const std::unordered_map<std::string, slang_semantic_map> &semantic_map,
|
|
const std::string &name, unsigned *index)
|
|
{
|
|
unsigned i = 0;
|
|
std::unordered_map<std::string, slang_semantic_map>::const_iterator itr =
|
|
semantic_map.find(name);
|
|
|
|
if (itr != semantic_map.end())
|
|
{
|
|
*index = itr->second.index;
|
|
return itr->second.semantic;
|
|
}
|
|
|
|
/* No builtin semantics are arrayed. */
|
|
*index = 0;
|
|
for (i = 0; i < sizeof(semantic_uniform_names) / sizeof(semantic_uniform_names[0]); i++)
|
|
{
|
|
if (name == semantic_uniform_names[i])
|
|
return static_cast<slang_semantic>(i);
|
|
}
|
|
|
|
return SLANG_INVALID_SEMANTIC;
|
|
}
|
|
|
|
template <typename T>
|
|
static void resize_minimum(T &vec, unsigned minimum)
|
|
{
|
|
if (vec.size() < minimum)
|
|
vec.resize(minimum);
|
|
}
|
|
|
|
|
|
static bool slang_process_reflection(
|
|
const spirv_cross::Compiler* vs_compiler,
|
|
const spirv_cross::Compiler* ps_compiler,
|
|
const spirv_cross::ShaderResources& vs_resources,
|
|
const spirv_cross::ShaderResources& ps_resources,
|
|
video_shader* shader_info,
|
|
unsigned pass_number,
|
|
const semantics_map_t* map,
|
|
pass_semantics_t* out)
|
|
{
|
|
int semantic;
|
|
unsigned i;
|
|
std::vector<texture_sem_t> textures;
|
|
std::vector<uniform_sem_t> uniforms[SLANG_CBUFFER_MAX];
|
|
std::unordered_map<std::string, slang_texture_semantic_map> texture_semantic_map;
|
|
std::unordered_map<std::string, slang_texture_semantic_map> texture_semantic_uniform_map;
|
|
|
|
for (i = 0; i < shader_info->passes; i++)
|
|
{
|
|
if (!*shader_info->pass[i].alias)
|
|
continue;
|
|
|
|
std::string name = shader_info->pass[i].alias;
|
|
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_map, name,
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT, i }))
|
|
return false;
|
|
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_uniform_map, name + "Size",
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT, i }))
|
|
return false;
|
|
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_map, name + "Feedback",
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK, i }))
|
|
return false;
|
|
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_uniform_map, name + "FeedbackSize",
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK, i }))
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < shader_info->luts; i++)
|
|
{
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_map, shader_info->lut[i].id,
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_USER, i }))
|
|
return false;
|
|
|
|
if (!slang_set_unique_map(
|
|
texture_semantic_uniform_map,
|
|
std::string(shader_info->lut[i].id) + "Size",
|
|
slang_texture_semantic_map{
|
|
SLANG_TEXTURE_SEMANTIC_USER, i }))
|
|
return false;
|
|
}
|
|
|
|
std::unordered_map<std::string, slang_semantic_map> uniform_semantic_map;
|
|
|
|
for (i = 0; i < shader_info->num_parameters; i++)
|
|
{
|
|
if (!slang_set_unique_map(
|
|
uniform_semantic_map, shader_info->parameters[i].id,
|
|
slang_semantic_map{ SLANG_SEMANTIC_FLOAT_PARAMETER, i }))
|
|
return false;
|
|
}
|
|
|
|
slang_reflection sl_reflection;
|
|
sl_reflection.pass_number = pass_number;
|
|
sl_reflection.texture_semantic_map = &texture_semantic_map;
|
|
sl_reflection.texture_semantic_uniform_map = &texture_semantic_uniform_map;
|
|
sl_reflection.semantic_map = &uniform_semantic_map;
|
|
|
|
if (!slang_reflect(*vs_compiler, *ps_compiler,
|
|
vs_resources, ps_resources, &sl_reflection))
|
|
{
|
|
RARCH_ERR("[Slang] Failed to reflect SPIR-V."
|
|
" Resource usage is inconsistent with "
|
|
"expectations.\n");
|
|
return false;
|
|
}
|
|
|
|
out->cbuffers[SLANG_CBUFFER_UBO].stage_mask = sl_reflection.ubo_stage_mask;
|
|
out->cbuffers[SLANG_CBUFFER_UBO].binding = sl_reflection.ubo_binding;
|
|
out->cbuffers[SLANG_CBUFFER_UBO].size = (unsigned)((sl_reflection.ubo_size + 0xF) & ~0xF);
|
|
out->cbuffers[SLANG_CBUFFER_PC].stage_mask = sl_reflection.push_constant_stage_mask;
|
|
out->cbuffers[SLANG_CBUFFER_PC].binding = sl_reflection.ubo_binding ? 0 : 1;
|
|
out->cbuffers[SLANG_CBUFFER_PC].size = (unsigned)((sl_reflection.push_constant_size + 0xF) & ~0xF);
|
|
|
|
for (semantic = 0; semantic < SLANG_NUM_BASE_SEMANTICS; semantic++)
|
|
{
|
|
slang_semantic_meta& src = sl_reflection.semantics[semantic];
|
|
if (src.push_constant || src.uniform)
|
|
{
|
|
uniform_sem_t uniform = { map->uniforms[semantic],
|
|
src.num_components
|
|
* (unsigned)sizeof(float) };
|
|
slang_semantic _semantic = (slang_semantic)semantic;
|
|
static const char* names[] = {
|
|
"MVP",
|
|
"OutputSize",
|
|
"FinalViewportSize",
|
|
"FrameCount",
|
|
"FrameDirection",
|
|
"FrameTimeDelta",
|
|
"OriginalFPS",
|
|
"Rotation",
|
|
"OriginalAspect",
|
|
"OriginalAspectRotated",
|
|
"TotalSubFrames",
|
|
"CurrentSubFrame",
|
|
"HDRMode",
|
|
"BrightnessNits",
|
|
"Scanlines",
|
|
"SubpixelLayout",
|
|
"ExpandGamut",
|
|
"InverseTonemap",
|
|
"HDR10",
|
|
/* Sensor uniforms: populated by GL3, GLSL, and Vulkan
|
|
* backends only. D3D/Metal/GX2 backends will see zero
|
|
* values (no hardware sensor support on those platforms). */
|
|
"Gyroscope",
|
|
"Accelerometer",
|
|
"AccelerometerRest",
|
|
};
|
|
int size = sizeof(names) / sizeof(*names);
|
|
if (semantic < size)
|
|
strlcpy(uniform.id, names[_semantic], sizeof(uniform.id));
|
|
else
|
|
strlcpy(uniform.id, get_semantic_name(sl_reflection.semantic_map, _semantic, 0), sizeof(uniform.id));
|
|
|
|
if (src.push_constant)
|
|
{
|
|
uniform.offset = (unsigned)src.push_constant_offset;
|
|
uniforms[SLANG_CBUFFER_PC].push_back(uniform);
|
|
}
|
|
else
|
|
{
|
|
uniform.offset = (unsigned)src.ubo_offset;
|
|
uniforms[SLANG_CBUFFER_UBO].push_back(uniform);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sl_reflection.semantic_float_parameters.size(); i++)
|
|
{
|
|
slang_semantic_meta& src = sl_reflection.semantic_float_parameters[i];
|
|
|
|
if (src.push_constant || src.uniform)
|
|
{
|
|
uniform_sem_t uniform = {
|
|
&shader_info->parameters[i].current, sizeof(float) };
|
|
strlcpy(uniform.id, get_semantic_name(sl_reflection.semantic_map, SLANG_SEMANTIC_FLOAT_PARAMETER, i), sizeof(uniform.id));
|
|
|
|
if (src.push_constant)
|
|
{
|
|
uniform.offset = (unsigned)src.push_constant_offset;
|
|
uniforms[SLANG_CBUFFER_PC].push_back(uniform);
|
|
}
|
|
else
|
|
{
|
|
uniform.offset = (unsigned)src.ubo_offset;
|
|
uniforms[SLANG_CBUFFER_UBO].push_back(uniform);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (semantic = 0; semantic < SLANG_NUM_TEXTURE_SEMANTICS; semantic++)
|
|
{
|
|
unsigned index;
|
|
|
|
for (index = 0; index <
|
|
sl_reflection.semantic_textures[semantic].size(); index++)
|
|
{
|
|
slang_texture_semantic_meta& src =
|
|
sl_reflection.semantic_textures[semantic][index];
|
|
|
|
if (src.stage_mask)
|
|
{
|
|
static const char* names[] = {
|
|
"Original", "Source", "OriginalHistory", "PassOutput", "PassFeedback",
|
|
};
|
|
texture_sem_t texture;
|
|
slang_texture_semantic
|
|
_semantic = (slang_texture_semantic)semantic;
|
|
texture.id[0] = '\0';
|
|
|
|
if (semantic == (int)SLANG_TEXTURE_SEMANTIC_ORIGINAL)
|
|
{
|
|
strlcpy(texture.id, names[semantic], sizeof(texture.id));
|
|
texture.wrap = shader_info->pass[0].wrap;
|
|
texture.filter = shader_info->pass[0].filter;
|
|
}
|
|
else if (semantic == (int)SLANG_TEXTURE_SEMANTIC_SOURCE)
|
|
{
|
|
strlcpy(texture.id, names[semantic], sizeof(texture.id));
|
|
texture.wrap = shader_info->pass[pass_number].wrap;
|
|
texture.filter = shader_info->pass[pass_number].filter;
|
|
}
|
|
else if (semantic == (int)SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY)
|
|
{
|
|
size_t _len = strlcpy(texture.id, names[semantic], sizeof(texture.id));
|
|
snprintf(texture.id + _len, sizeof(texture.id) - _len, "%d", index);
|
|
texture.wrap = shader_info->pass[0].wrap;
|
|
texture.filter = shader_info->pass[0].filter;
|
|
}
|
|
else if (semantic == (int)SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT)
|
|
{
|
|
size_t _len = strlcpy(texture.id, names[semantic], sizeof(texture.id));
|
|
snprintf(texture.id + _len, sizeof(texture.id) - _len, "%d", index);
|
|
if ((index + 1) < shader_info->passes)
|
|
{
|
|
texture.wrap = shader_info->pass[index + 1].wrap;
|
|
texture.filter = shader_info->pass[index + 1].filter;
|
|
}
|
|
else // should not happen and already be checked
|
|
{
|
|
texture.wrap = shader_info->pass[index].wrap;
|
|
texture.filter = shader_info->pass[index].filter;
|
|
}
|
|
}
|
|
else if (semantic == (int)SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK)
|
|
{
|
|
size_t _len = strlcpy(texture.id, names[semantic], sizeof(texture.id));
|
|
snprintf(texture.id + _len, sizeof(texture.id) - _len, "%d", index);
|
|
texture.wrap = shader_info->pass[index].wrap;
|
|
texture.filter = shader_info->pass[index].filter;
|
|
}
|
|
else // SLANG_TEXTURE_SEMANTIC_USER
|
|
{
|
|
strlcpy(texture.id, get_semantic_name(sl_reflection.texture_semantic_map, _semantic, index), sizeof(texture.id));
|
|
texture.wrap = shader_info->lut[index].wrap;
|
|
texture.filter = shader_info->lut[index].filter;
|
|
}
|
|
|
|
texture.texture_data =
|
|
(void*)((uintptr_t)map->textures[semantic].image + index * map->textures[semantic].image_stride);
|
|
|
|
texture.stage_mask = src.stage_mask;
|
|
texture.binding = src.binding;
|
|
|
|
textures.push_back(texture);
|
|
|
|
if (semantic == SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK)
|
|
shader_info->pass[index].feedback = true;
|
|
|
|
if (semantic == SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY &&
|
|
(unsigned)shader_info->history_size < index)
|
|
shader_info->history_size = index;
|
|
}
|
|
|
|
if (src.push_constant || src.uniform)
|
|
{
|
|
uniform_sem_t uniform = {
|
|
(void*)((uintptr_t)map->textures[semantic].size
|
|
+ index * map->textures[semantic].size_stride),
|
|
4 * sizeof(float)
|
|
};
|
|
slang_texture_semantic _semantic = (slang_texture_semantic)semantic;
|
|
static const char* names[] = {
|
|
"OriginalSize", "SourceSize", "OriginalHistorySize", "PassOutputSize", "PassFeedbackSize",
|
|
};
|
|
if (semantic < (int)SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY)
|
|
strlcpy(uniform.id, names[_semantic], sizeof(uniform.id));
|
|
else
|
|
{
|
|
int size = sizeof(names) / sizeof(*names);
|
|
if (semantic < size)
|
|
{
|
|
size_t _len = strlcpy(uniform.id, names[_semantic], sizeof(uniform.id));
|
|
snprintf(uniform.id + _len, sizeof(uniform.id) - _len, "%d", index);
|
|
}
|
|
else
|
|
strlcpy(uniform.id, get_semantic_name(sl_reflection.texture_semantic_uniform_map, _semantic, index), sizeof(uniform.id));
|
|
}
|
|
|
|
if (src.push_constant)
|
|
{
|
|
uniform.offset = (unsigned)src.push_constant_offset;
|
|
uniforms[SLANG_CBUFFER_PC].push_back(uniform);
|
|
}
|
|
else
|
|
{
|
|
uniform.offset = (unsigned)src.ubo_offset;
|
|
uniforms[SLANG_CBUFFER_UBO].push_back(uniform);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out->texture_count = (int)textures.size();
|
|
|
|
textures.push_back({ NULL });
|
|
out->textures = (texture_sem_t*)
|
|
malloc(textures.size() * sizeof(*textures.data()));
|
|
memcpy(out->textures, textures.data(),
|
|
textures.size() * sizeof(*textures.data()));
|
|
|
|
for (i = 0; i < SLANG_CBUFFER_MAX; i++)
|
|
{
|
|
if (uniforms[i].empty())
|
|
continue;
|
|
|
|
out->cbuffers[i].uniform_count = (int)uniforms[i].size();
|
|
|
|
uniforms[i].push_back({ NULL });
|
|
out->cbuffers[i].uniforms =
|
|
(uniform_sem_t*)
|
|
malloc(uniforms[i].size() * sizeof(*uniforms[i].data()));
|
|
|
|
memcpy(
|
|
out->cbuffers[i].uniforms, uniforms[i].data(),
|
|
uniforms[i].size() * sizeof(*uniforms[i].data()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::string build_stage_source(
|
|
const struct shader_line_buf *lines, const char *stage)
|
|
{
|
|
size_t i;
|
|
std::string str;
|
|
bool active = true;
|
|
if (!lines || lines->num_lines < 1)
|
|
return "";
|
|
str.reserve(lines->len);
|
|
/* Version header (line 0). */
|
|
str.append(shader_line_buf_get(lines, 0));
|
|
str.append("\n");
|
|
for (i = 1; i < lines->num_lines; i++)
|
|
{
|
|
const char *line = shader_line_buf_get(lines, i);
|
|
if (!memcmp(line, "#pragma", sizeof("#pragma")-1))
|
|
{
|
|
if (!memcmp(line, "#pragma stage ", sizeof("#pragma stage ")-1))
|
|
{
|
|
if (stage && *stage)
|
|
{
|
|
char expected[128];
|
|
size_t _len = strlcpy_lit(expected, "#pragma stage ", sizeof(expected));
|
|
strlcpy(expected + _len, stage, sizeof(expected) - _len);
|
|
active = !strcmp(expected, line);
|
|
}
|
|
}
|
|
else if (
|
|
!memcmp(line, "#pragma name ", sizeof("#pragma name ")-1)
|
|
|| !memcmp(line, "#pragma format ", sizeof("#pragma format ")-1))
|
|
{
|
|
/* Ignore */
|
|
}
|
|
else if (active)
|
|
{
|
|
str.append(line);
|
|
str.append("\n");
|
|
}
|
|
}
|
|
else if (active)
|
|
{
|
|
str.append(line);
|
|
str.append("\n");
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static bool glslang_parse_meta(const struct shader_line_buf *lines,
|
|
glslang_meta *meta)
|
|
{
|
|
char id[64];
|
|
char desc[64];
|
|
size_t i;
|
|
|
|
id[0] = '\0';
|
|
desc[0] = '\0';
|
|
|
|
/* Pre-count parameters to avoid vector reallocation */
|
|
{
|
|
size_t param_count = 0;
|
|
for (i = 0; i < lines->num_lines; i++)
|
|
{
|
|
const char *line = shader_line_buf_get(lines, i);
|
|
if (line && !memcmp(line, "#pragma parameter ",
|
|
sizeof("#pragma parameter ") - 1))
|
|
param_count++;
|
|
}
|
|
if (param_count > 0)
|
|
meta->parameters.reserve(param_count);
|
|
}
|
|
|
|
for (i = 0; i < lines->num_lines; i++)
|
|
{
|
|
const char *line = shader_line_buf_get(lines, i);
|
|
if (!line)
|
|
continue;
|
|
|
|
if (memcmp(line, "#pragma", sizeof("#pragma") - 1))
|
|
continue;
|
|
|
|
/* Check for shader identifier */
|
|
if (!memcmp(line, "#pragma name ",
|
|
sizeof("#pragma name ") - 1))
|
|
{
|
|
const char *str = line + (sizeof("#pragma name ") - 1);
|
|
while (*str == ' ' || *str == '\t')
|
|
str++;
|
|
if (!meta->name.empty())
|
|
{
|
|
RARCH_ERR("[Slang] Trying to declare multiple names for file.\n");
|
|
return false;
|
|
}
|
|
meta->name = str;
|
|
}
|
|
/* Check for shader parameters */
|
|
else if (!memcmp(line, "#pragma parameter ",
|
|
sizeof("#pragma parameter ") - 1))
|
|
{
|
|
float initial, minimum, maximum, step;
|
|
int fields = 0;
|
|
const char *s = line + (sizeof("#pragma parameter ") - 1);
|
|
size_t len = 0;
|
|
size_t id_len, desc_len;
|
|
char *end = NULL;
|
|
|
|
/* Parse id */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
len = 0;
|
|
while (s[len] && s[len] != ' ' && s[len] != '\t')
|
|
len++;
|
|
if (len == 0 || len >= sizeof(id))
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
memcpy(id, s, len);
|
|
id[len] = '\0';
|
|
id_len = len;
|
|
s += len;
|
|
|
|
/* Parse quoted description */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
if (*s != '"')
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
s++;
|
|
len = 0;
|
|
while (s[len] && s[len] != '"')
|
|
len++;
|
|
if (s[len] != '"' || len >= sizeof(desc))
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
memcpy(desc, s, len);
|
|
desc[len] = '\0';
|
|
desc_len = len;
|
|
s += len + 1;
|
|
|
|
/* Parse initial */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
initial = (float)strtod(s, &end);
|
|
if (end == s)
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
s = end;
|
|
|
|
/* Parse minimum */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
minimum = (float)strtod(s, &end);
|
|
if (end == s)
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
s = end;
|
|
|
|
/* Parse maximum */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
maximum = (float)strtod(s, &end);
|
|
if (end == s)
|
|
{
|
|
RARCH_ERR("[Slang] Invalid #pragma parameter line: \"%s\".\n", line);
|
|
return false;
|
|
}
|
|
s = end;
|
|
fields = 5;
|
|
|
|
/* Parse step (optional) */
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
if (*s)
|
|
{
|
|
step = (float)strtod(s, &end);
|
|
if (end != s)
|
|
fields = 6;
|
|
}
|
|
|
|
if (fields == 5)
|
|
{
|
|
step = 0.1f * (maximum - minimum);
|
|
fields = 6;
|
|
}
|
|
|
|
{
|
|
bool parameter_found = false;
|
|
size_t parameter_index = 0;
|
|
size_t j;
|
|
|
|
for (j = 0; j < meta->parameters.size(); j++)
|
|
{
|
|
const std::string &pid = meta->parameters[j].id;
|
|
if (pid.size() == id_len
|
|
&& !memcmp(pid.data(), id, id_len))
|
|
{
|
|
parameter_found = true;
|
|
parameter_index = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Allow duplicate #pragma parameter, but only
|
|
* if they are exactly the same. */
|
|
if (parameter_found)
|
|
{
|
|
const glslang_parameter *parameter =
|
|
&meta->parameters[parameter_index];
|
|
if ( parameter->desc.size() != desc_len
|
|
|| memcmp(parameter->desc.data(), desc, desc_len)
|
|
|| (parameter->initial != initial)
|
|
|| (parameter->minimum != minimum)
|
|
|| (parameter->maximum != maximum)
|
|
|| (parameter->step != step)
|
|
)
|
|
{
|
|
RARCH_ERR("[Slang] Duplicate parameters"
|
|
" found for \"%s\", but arguments"
|
|
" do not match.\n", id);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glslang_parameter p;
|
|
p.id.assign(id, id_len);
|
|
p.desc.assign(desc, desc_len);
|
|
p.initial = initial;
|
|
p.minimum = minimum;
|
|
p.maximum = maximum;
|
|
p.step = step;
|
|
meta->parameters.push_back(p);
|
|
}
|
|
}
|
|
}
|
|
/* Check for framebuffer format */
|
|
else if (!memcmp(line, "#pragma format ",
|
|
sizeof("#pragma format ") - 1))
|
|
{
|
|
const char *str = line + (sizeof("#pragma format ") - 1);
|
|
while (*str == ' ' || *str == '\t')
|
|
str++;
|
|
if (meta->rt_format != SLANG_FORMAT_UNKNOWN)
|
|
{
|
|
RARCH_ERR("[Slang] Trying to declare format"
|
|
" multiple times for file.\n");
|
|
return false;
|
|
}
|
|
meta->rt_format = glslang_find_format(str);
|
|
if (meta->rt_format == SLANG_FORMAT_UNKNOWN)
|
|
{
|
|
RARCH_ERR("[Slang] Failed to find format \"%s\".\n", str);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------
|
|
* glslang_compile_shader — now uses shader_line_buf
|
|
* ----------------------------------------------------------------------- */
|
|
bool glslang_compile_shader(const char *shader_path, glslang_output *output)
|
|
{
|
|
return glslang_compile_shader_cached(shader_path, output, NULL);
|
|
}
|
|
|
|
bool glslang_compile_shader_cached(const char *shader_path,
|
|
glslang_output *output, void *include_cache)
|
|
{
|
|
#if defined(HAVE_GLSLANG)
|
|
struct shader_line_buf lines;
|
|
char cache_filename[PATH_MAX_LENGTH];
|
|
|
|
if (!shader_line_buf_init(&lines))
|
|
return false;
|
|
|
|
RARCH_LOG("[Slang] Compiling shader: \"%s\".\n", shader_path);
|
|
|
|
if (!glslang_read_shader_file_cached(shader_path, &lines, true, false,
|
|
include_cache))
|
|
goto error;
|
|
|
|
/* Compute cache key from preprocessed source (vertex + fragment stages) */
|
|
{
|
|
std::string vertex_source = build_stage_source(&lines, "vertex");
|
|
std::string fragment_source = build_stage_source(&lines, "fragment");
|
|
spirv_cache_compute_hash(vertex_source.c_str(), fragment_source.c_str(),
|
|
cache_filename);
|
|
}
|
|
|
|
/* Try to load from cache */
|
|
if (spirv_cache_load(cache_filename, output))
|
|
{
|
|
RARCH_LOG("[Slang] Loaded shader from cache: \"%s\".\n", shader_path);
|
|
shader_line_buf_free(&lines);
|
|
return true;
|
|
}
|
|
|
|
output->meta = glslang_meta{};
|
|
if (!glslang_parse_meta(&lines, &output->meta))
|
|
goto error;
|
|
|
|
if (!glslang::compile_spirv(build_stage_source(&lines, "vertex"),
|
|
glslang::StageVertex, &output->vertex))
|
|
{
|
|
RARCH_ERR("[Slang] Failed to compile vertex shader stage.\n");
|
|
goto error;
|
|
}
|
|
|
|
if (!glslang::compile_spirv(build_stage_source(&lines, "fragment"),
|
|
glslang::StageFragment, &output->fragment))
|
|
{
|
|
RARCH_ERR("[Slang] Failed to compile fragment shader stage.\n");
|
|
goto error;
|
|
}
|
|
|
|
/* Save to cache */
|
|
spirv_cache_save(cache_filename, output);
|
|
|
|
shader_line_buf_free(&lines);
|
|
|
|
return true;
|
|
|
|
error:
|
|
shader_line_buf_free(&lines);
|
|
#endif
|
|
|
|
{
|
|
size_t _len;
|
|
char msg[NAME_MAX_LENGTH];
|
|
|
|
_len = snprintf(msg, sizeof(msg), "Failed to compile shader: \"%s\".",
|
|
path_basename(shader_path));
|
|
|
|
runloop_msg_queue_push(msg, _len, 1, 120, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool slang_preprocess_parse_parameters(glslang_meta& meta,
|
|
struct video_shader *shader)
|
|
{
|
|
unsigned i;
|
|
unsigned old_num_parameters = shader->num_parameters;
|
|
|
|
/* Assumes num_parameters is
|
|
* initialized to something sane. */
|
|
for (i = 0; i < meta.parameters.size(); i++)
|
|
{
|
|
unsigned k;
|
|
struct video_shader_parameter *p = NULL;
|
|
bool mismatch_dup = false;
|
|
for (k = 0; k < shader->num_parameters; k++)
|
|
{
|
|
if (meta.parameters[i].id == shader->parameters[k].id)
|
|
{
|
|
p = &shader->parameters[k];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p != NULL)
|
|
{
|
|
/* Allow duplicate #pragma parameter, but only
|
|
* if they are exactly the same. */
|
|
if ( meta.parameters[i].desc != p->desc
|
|
|| meta.parameters[i].initial != p->initial
|
|
|| meta.parameters[i].minimum != p->minimum
|
|
|| meta.parameters[i].maximum != p->maximum
|
|
|| meta.parameters[i].step != p->step)
|
|
{
|
|
RARCH_ERR("[Slang] Duplicate parameters"
|
|
" found for \"%s\", but arguments do not match.\n",
|
|
p->id);
|
|
mismatch_dup = true;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
if (mismatch_dup || shader->num_parameters == GFX_MAX_PARAMETERS)
|
|
{
|
|
shader->num_parameters = old_num_parameters;
|
|
return false;
|
|
}
|
|
|
|
if (!(p = (struct video_shader_parameter*)
|
|
&shader->parameters[shader->num_parameters++]))
|
|
continue;
|
|
|
|
strlcpy(p->id, meta.parameters[i].id.c_str(), sizeof(p->id));
|
|
strlcpy(p->desc, meta.parameters[i].desc.c_str(), sizeof(p->desc));
|
|
p->initial = meta.parameters[i].initial;
|
|
p->minimum = meta.parameters[i].minimum;
|
|
p->maximum = meta.parameters[i].maximum;
|
|
p->step = meta.parameters[i].step;
|
|
p->current = meta.parameters[i].initial;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------
|
|
* slang_preprocess_parse_parameters (C-linkage overload) — uses shader_line_buf
|
|
* ----------------------------------------------------------------------- */
|
|
bool slang_preprocess_parse_parameters(const char *shader_path,
|
|
struct video_shader *shader)
|
|
{
|
|
return slang_preprocess_parse_parameters_cached(shader_path, shader,
|
|
NULL);
|
|
}
|
|
|
|
bool slang_preprocess_parse_parameters_cached(const char *shader_path,
|
|
struct video_shader *shader, void *include_cache)
|
|
{
|
|
struct shader_line_buf lines;
|
|
|
|
memset(&lines, 0, sizeof(lines));
|
|
|
|
if (shader_line_buf_init(&lines))
|
|
{
|
|
if (glslang_read_shader_file_cached(shader_path, &lines, true, false,
|
|
include_cache))
|
|
{
|
|
glslang_meta meta = glslang_meta{};
|
|
if (glslang_parse_meta(&lines, &meta))
|
|
{
|
|
shader_line_buf_free(&lines);
|
|
return slang_preprocess_parse_parameters(meta, shader);
|
|
}
|
|
}
|
|
}
|
|
|
|
shader_line_buf_free(&lines);
|
|
return false;
|
|
}
|
|
|
|
bool slang_process(
|
|
video_shader* shader_info,
|
|
unsigned pass_number,
|
|
enum rarch_shader_type dst_type,
|
|
unsigned version,
|
|
const semantics_map_t* semantics_map,
|
|
pass_semantics_t* out)
|
|
{
|
|
glslang_output output;
|
|
spirv_cross::Compiler *vs_compiler = NULL;
|
|
spirv_cross::Compiler *ps_compiler = NULL;
|
|
video_shader_pass &pass = shader_info->pass[pass_number];
|
|
|
|
if (!glslang_compile_shader(pass.source.path, &output))
|
|
return false;
|
|
|
|
if (!slang_preprocess_parse_parameters(output.meta, shader_info))
|
|
return false;
|
|
|
|
if (!*pass.alias && !output.meta.name.empty())
|
|
strlcpy(pass.alias, output.meta.name.c_str(), sizeof(pass.alias) - 1);
|
|
|
|
out->format = output.meta.rt_format;
|
|
out->explicit_format = (output.meta.rt_format != SLANG_FORMAT_UNKNOWN);
|
|
|
|
if (out->format == SLANG_FORMAT_UNKNOWN)
|
|
{
|
|
if (pass.fbo.flags & FBO_SCALE_FLAG_SRGB_FBO)
|
|
out->format = SLANG_FORMAT_R8G8B8A8_SRGB;
|
|
else if (pass.fbo.flags & FBO_SCALE_FLAG_FP_FBO)
|
|
out->format = SLANG_FORMAT_R16G16B16A16_SFLOAT;
|
|
else if (pass.fbo.flags & FBO_SCALE_FLAG_RGB10_FBO)
|
|
out->format = SLANG_FORMAT_A2B10G10R10_UNORM_PACK32;
|
|
else
|
|
out->format = SLANG_FORMAT_R8G8B8A8_UNORM;
|
|
}
|
|
|
|
pass.source.string.vertex = NULL;
|
|
pass.source.string.fragment = NULL;
|
|
|
|
try
|
|
{
|
|
spirv_cross::ShaderResources vs_resources;
|
|
spirv_cross::ShaderResources ps_resources;
|
|
std::string vs_code;
|
|
std::string ps_code;
|
|
|
|
switch (dst_type)
|
|
{
|
|
case RARCH_SHADER_HLSL:
|
|
case RARCH_SHADER_CG:
|
|
#ifdef HAVE_HLSL
|
|
vs_compiler = new spirv_cross::CompilerHLSL(output.vertex);
|
|
ps_compiler = new spirv_cross::CompilerHLSL(output.fragment);
|
|
#endif
|
|
break;
|
|
|
|
case RARCH_SHADER_METAL:
|
|
vs_compiler = new spirv_cross::CompilerMSL(output.vertex);
|
|
ps_compiler = new spirv_cross::CompilerMSL(output.fragment);
|
|
break;
|
|
|
|
default:
|
|
vs_compiler = new spirv_cross::CompilerGLSL(output.vertex);
|
|
ps_compiler = new spirv_cross::CompilerGLSL(output.fragment);
|
|
break;
|
|
}
|
|
|
|
if (vs_compiler)
|
|
vs_resources = vs_compiler->get_shader_resources();
|
|
if (ps_compiler)
|
|
ps_resources = ps_compiler->get_shader_resources();
|
|
|
|
if (!vs_resources.uniform_buffers.empty())
|
|
vs_compiler->set_decoration(
|
|
vs_resources.uniform_buffers[0].id, spv::DecorationBinding, 0);
|
|
if (!ps_resources.uniform_buffers.empty())
|
|
ps_compiler->set_decoration(
|
|
ps_resources.uniform_buffers[0].id, spv::DecorationBinding, 0);
|
|
|
|
if (!vs_resources.push_constant_buffers.empty())
|
|
vs_compiler->set_decoration(
|
|
vs_resources.push_constant_buffers[0].id, spv::DecorationBinding, 1);
|
|
if (!ps_resources.push_constant_buffers.empty())
|
|
ps_compiler->set_decoration(
|
|
ps_resources.push_constant_buffers[0].id, spv::DecorationBinding, 1);
|
|
|
|
switch (dst_type)
|
|
{
|
|
case RARCH_SHADER_HLSL:
|
|
case RARCH_SHADER_CG:
|
|
#ifdef HAVE_HLSL
|
|
{
|
|
spirv_cross::CompilerHLSL::Options options;
|
|
spirv_cross::CompilerHLSL *vs = (spirv_cross::CompilerHLSL*)vs_compiler;
|
|
spirv_cross::CompilerHLSL *ps = (spirv_cross::CompilerHLSL*)ps_compiler;
|
|
options.shader_model = version;
|
|
vs->set_hlsl_options(options);
|
|
ps->set_hlsl_options(options);
|
|
vs_code = vs->compile();
|
|
ps_code = ps->compile();
|
|
}
|
|
#endif
|
|
break;
|
|
case RARCH_SHADER_METAL:
|
|
{
|
|
spirv_cross::CompilerMSL::Options options;
|
|
spirv_cross::CompilerMSL *vs = (spirv_cross::CompilerMSL*)vs_compiler;
|
|
spirv_cross::CompilerMSL *ps = (spirv_cross::CompilerMSL*)ps_compiler;
|
|
options.msl_version = version;
|
|
vs->set_msl_options(options);
|
|
ps->set_msl_options(options);
|
|
|
|
const auto remap_push_constant = [](spirv_cross::CompilerMSL *comp,
|
|
const spirv_cross::ShaderResources &resources) {
|
|
for (const spirv_cross::Resource& resource : resources.push_constant_buffers)
|
|
{
|
|
/* Explicit 1:1 mapping for bindings. */
|
|
spirv_cross::MSLResourceBinding binding;
|
|
binding.stage = comp->get_execution_model();
|
|
binding.desc_set = spirv_cross::kPushConstDescSet;
|
|
binding.binding = spirv_cross::kPushConstBinding;
|
|
/* Use earlier decoration override. */
|
|
binding.basetype = spirv_cross::SPIRType::Unknown;
|
|
binding.count = 0;
|
|
binding.msl_buffer = comp->get_decoration(
|
|
resource.id, spv::DecorationBinding);
|
|
binding.msl_texture = 0;
|
|
binding.msl_sampler = 0;
|
|
comp->add_msl_resource_binding(binding);
|
|
}
|
|
};
|
|
|
|
const auto remap_generic_resource = [](spirv_cross::CompilerMSL *comp,
|
|
const spirv_cross::SmallVector<spirv_cross::Resource> &resources) {
|
|
for (const spirv_cross::Resource& resource : resources)
|
|
{
|
|
/* Explicit 1:1 mapping for bindings. */
|
|
spirv_cross::MSLResourceBinding binding;
|
|
binding.stage = comp->get_execution_model();
|
|
binding.desc_set = comp->get_decoration(
|
|
resource.id, spv::DecorationDescriptorSet);
|
|
binding.basetype = spirv_cross::SPIRType::Unknown;
|
|
binding.count = 0;
|
|
|
|
/* Use existing decoration override. */
|
|
uint32_t msl_binding = comp->get_decoration(
|
|
resource.id, spv::DecorationBinding);
|
|
binding.binding = msl_binding;
|
|
binding.msl_buffer = msl_binding;
|
|
binding.msl_texture = msl_binding;
|
|
binding.msl_sampler = msl_binding;
|
|
comp->add_msl_resource_binding(binding);
|
|
}
|
|
};
|
|
|
|
remap_push_constant(vs, vs_resources);
|
|
remap_push_constant(ps, ps_resources);
|
|
remap_generic_resource(vs, vs_resources.uniform_buffers);
|
|
remap_generic_resource(ps, ps_resources.uniform_buffers);
|
|
remap_generic_resource(vs, vs_resources.sampled_images);
|
|
remap_generic_resource(ps, ps_resources.sampled_images);
|
|
|
|
vs_code = vs->compile();
|
|
ps_code = ps->compile();
|
|
}
|
|
break;
|
|
case RARCH_SHADER_GLSL:
|
|
{
|
|
spirv_cross::CompilerGLSL::Options options;
|
|
spirv_cross::CompilerGLSL *vs = (spirv_cross::CompilerGLSL*)vs_compiler;
|
|
spirv_cross::CompilerGLSL *ps = (spirv_cross::CompilerGLSL*)ps_compiler;
|
|
options.version = version;
|
|
ps->set_common_options(options);
|
|
vs->set_common_options(options);
|
|
|
|
vs_code = vs->compile();
|
|
ps_code = ps->compile();
|
|
}
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
|
|
pass.source.string.vertex = strdup(vs_code.c_str());
|
|
pass.source.string.fragment = strdup(ps_code.c_str());
|
|
|
|
if (!slang_process_reflection(
|
|
vs_compiler, ps_compiler,
|
|
vs_resources, ps_resources, shader_info, pass_number,
|
|
semantics_map, out))
|
|
goto error;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
RARCH_ERR("[Slang] SPIRV-Cross threw exception: %s.\n", e.what());
|
|
goto error;
|
|
}
|
|
|
|
delete vs_compiler;
|
|
delete ps_compiler;
|
|
|
|
return true;
|
|
|
|
error:
|
|
free(pass.source.string.vertex);
|
|
free(pass.source.string.fragment);
|
|
|
|
pass.source.string.vertex = NULL;
|
|
pass.source.string.fragment = NULL;
|
|
|
|
delete vs_compiler;
|
|
delete ps_compiler;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool set_ubo_texture_offset(
|
|
slang_reflection *reflection,
|
|
slang_texture_semantic semantic,
|
|
unsigned index,
|
|
size_t offset, bool push_constant)
|
|
{
|
|
resize_minimum(reflection->semantic_textures[semantic], index + 1);
|
|
slang_texture_semantic_meta &sem = reflection->semantic_textures[semantic][index];
|
|
bool &active = push_constant ? sem.push_constant : sem.uniform;
|
|
size_t &active_offset = push_constant ? sem.push_constant_offset : sem.ubo_offset;
|
|
|
|
if (active)
|
|
{
|
|
if (active_offset != offset)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment have"
|
|
" different offsets for same semantic %s #%u (%u vs. %u).\n",
|
|
texture_semantic_uniform_names[semantic],
|
|
index,
|
|
unsigned(active_offset),
|
|
unsigned(offset));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
active = true;
|
|
active_offset = offset;
|
|
return true;
|
|
}
|
|
|
|
static bool set_ubo_float_parameter_offset(
|
|
slang_reflection *reflection,
|
|
unsigned index, size_t offset,
|
|
unsigned num_components,
|
|
bool push_constant)
|
|
{
|
|
resize_minimum(reflection->semantic_float_parameters, index + 1);
|
|
slang_semantic_meta &sem = reflection->semantic_float_parameters[index];
|
|
bool &active = push_constant ? sem.push_constant : sem.uniform;
|
|
size_t &active_offset = push_constant ? sem.push_constant_offset : sem.ubo_offset;
|
|
|
|
if (active)
|
|
{
|
|
if (active_offset != offset)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment have different"
|
|
" offsets for same parameter #%u (%u vs. %u).\n",
|
|
index,
|
|
unsigned(active_offset),
|
|
unsigned(offset));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( (sem.num_components != num_components) &&
|
|
(sem.uniform || sem.push_constant))
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment have different "
|
|
"components for same parameter #%u (%u vs. %u).\n",
|
|
index,
|
|
unsigned(sem.num_components),
|
|
unsigned(num_components));
|
|
return false;
|
|
}
|
|
|
|
active = true;
|
|
active_offset = offset;
|
|
sem.num_components = num_components;
|
|
return true;
|
|
}
|
|
|
|
static bool set_ubo_offset(
|
|
slang_reflection *reflection,
|
|
slang_semantic semantic,
|
|
size_t offset, unsigned num_components, bool push_constant)
|
|
{
|
|
slang_semantic_meta &sem = reflection->semantics[semantic];
|
|
bool &active = push_constant ? sem.push_constant : sem.uniform;
|
|
size_t &active_offset = push_constant ? sem.push_constant_offset : sem.ubo_offset;
|
|
|
|
if (active)
|
|
{
|
|
if (active_offset != offset)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment have "
|
|
"different offsets for same semantic %s (%u vs. %u).\n",
|
|
semantic_uniform_names[semantic],
|
|
unsigned(active_offset),
|
|
unsigned(offset));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( (sem.num_components != num_components) &&
|
|
(sem.uniform || sem.push_constant))
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment have different"
|
|
" components for same semantic %s (%u vs. %u).\n",
|
|
semantic_uniform_names[semantic],
|
|
unsigned(sem.num_components),
|
|
unsigned(num_components));
|
|
return false;
|
|
}
|
|
|
|
active = true;
|
|
active_offset = offset;
|
|
sem.num_components = num_components;
|
|
return true;
|
|
}
|
|
|
|
static bool validate_type_for_semantic(const spirv_cross::SPIRType &type, slang_semantic sem)
|
|
{
|
|
if (!type.array.empty())
|
|
return false;
|
|
if ( type.basetype != spirv_cross::SPIRType::Float
|
|
&& type.basetype != spirv_cross::SPIRType::Int
|
|
&& type.basetype != spirv_cross::SPIRType::UInt)
|
|
return false;
|
|
|
|
switch (sem)
|
|
{
|
|
/* mat4 */
|
|
case SLANG_SEMANTIC_MVP:
|
|
return
|
|
type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 4
|
|
&& type.columns == 4;
|
|
/* uint */
|
|
case SLANG_SEMANTIC_FRAME_COUNT:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* int */
|
|
case SLANG_SEMANTIC_TOTAL_SUBFRAMES:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* int */
|
|
case SLANG_SEMANTIC_CURRENT_SUBFRAME:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* int */
|
|
case SLANG_SEMANTIC_FRAME_DIRECTION:
|
|
return type.basetype == spirv_cross::SPIRType::Int
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* uint */
|
|
case SLANG_SEMANTIC_FRAME_TIME_DELTA:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* uint */
|
|
case SLANG_SEMANTIC_ORIGINAL_FPS:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* uint */
|
|
case SLANG_SEMANTIC_ROTATION:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_CORE_ASPECT:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_CORE_ASPECT_ROT:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* vec3 - sensor uniforms */
|
|
case SLANG_SEMANTIC_GYROSCOPE:
|
|
case SLANG_SEMANTIC_ACCELEROMETER:
|
|
case SLANG_SEMANTIC_ACCELEROMETER_REST:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 3
|
|
&& type.columns == 1;
|
|
/* float */
|
|
case SLANG_SEMANTIC_FLOAT_PARAMETER:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_HDR:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_PAPER_WHITE_NITS:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_SCANLINES:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_SUBPIXEL_LAYOUT:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_EXPAND_GAMUT:
|
|
return type.basetype == spirv_cross::SPIRType::UInt
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_INVERSE_TONEMAP:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
case SLANG_SEMANTIC_HDR10:
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 1
|
|
&& type.columns == 1;
|
|
/* vec4 */
|
|
default:
|
|
break;
|
|
}
|
|
return type.basetype == spirv_cross::SPIRType::Float
|
|
&& type.vecsize == 4
|
|
&& type.columns == 1;
|
|
}
|
|
|
|
static bool validate_type_for_texture_semantic(const spirv_cross::SPIRType &type)
|
|
{
|
|
return (type.array.empty())
|
|
&& (type.basetype == spirv_cross::SPIRType::Float)
|
|
&& (type.vecsize == 4)
|
|
&& (type.columns == 1);
|
|
}
|
|
|
|
/* Validate that a texture semantic's array index falls inside the
|
|
* range admitted by the host filter chain. The shader source is
|
|
* untrusted (preset packs are downloadable / shipped third-party),
|
|
* and the index suffix in arrayed semantic names like
|
|
* `OriginalHistory42` / `PassFeedback9` / `User7` is parsed via
|
|
* strtoul in slang_name_to_texture_semantic_array() with no upper
|
|
* bound. The downstream resize_minimum() call in
|
|
* set_ubo_texture_offset() and the direct-binding loop below would
|
|
* otherwise grow reflection->semantic_textures[sem] to the index+1
|
|
* the shader requested -- which on a malicious preset can be near
|
|
* UINT32_MAX, producing an unhandled std::bad_alloc that terminates
|
|
* RetroArch. PASS_OUTPUT was already bounded against
|
|
* reflection->pass_number; extend the same defensive shape to the
|
|
* other arrayed semantics with their natural caps. USER is the
|
|
* lookup name for LUTs, ORIGINAL_HISTORY is bounded by the per-
|
|
* frame ring used by init_history(), and PASS_FEEDBACK is bounded
|
|
* by the maximum number of passes the chain can hold. */
|
|
static bool validate_texture_semantic_index(slang_reflection *reflection,
|
|
slang_texture_semantic tex_sem, unsigned index)
|
|
{
|
|
unsigned cap = 0;
|
|
const char *cap_label = NULL;
|
|
|
|
switch (tex_sem)
|
|
{
|
|
case SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT:
|
|
cap = reflection->pass_number;
|
|
cap_label = "preceding passes";
|
|
break;
|
|
case SLANG_TEXTURE_SEMANTIC_PASS_FEEDBACK:
|
|
cap = GFX_MAX_SHADERS;
|
|
cap_label = "GFX_MAX_SHADERS";
|
|
break;
|
|
case SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY:
|
|
cap = GFX_MAX_FRAME_HISTORY;
|
|
cap_label = "GFX_MAX_FRAME_HISTORY";
|
|
break;
|
|
case SLANG_TEXTURE_SEMANTIC_USER:
|
|
cap = GFX_MAX_TEXTURES;
|
|
cap_label = "GFX_MAX_TEXTURES";
|
|
break;
|
|
default:
|
|
/* Non-arrayed semantics (Original, Source) -- index is
|
|
* always 0 by construction in slang_name_to_texture_
|
|
* semantic_array(). */
|
|
return true;
|
|
}
|
|
|
|
if (index >= cap)
|
|
{
|
|
if (tex_sem == SLANG_TEXTURE_SEMANTIC_PASS_OUTPUT)
|
|
RARCH_ERR("[Slang] Non causal filter chain detected. "
|
|
"Shader is trying to use output from pass #%u,"
|
|
" but this shader is pass #%u.\n",
|
|
index, reflection->pass_number);
|
|
else
|
|
RARCH_ERR("[Slang] Texture semantic %s index #%u exceeds"
|
|
" bound (%s = %u).\n",
|
|
texture_semantic_names[tex_sem],
|
|
index, cap_label, cap);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool add_active_buffer_ranges(
|
|
const spirv_cross::Compiler &compiler,
|
|
const spirv_cross::Resource &resource,
|
|
slang_reflection *reflection,
|
|
bool push_constant)
|
|
{
|
|
unsigned i;
|
|
/* Get which uniforms are actually in use by this shader. */
|
|
spirv_cross::SmallVector<spirv_cross::BufferRange> ranges =
|
|
compiler.get_active_buffer_ranges(resource.id);
|
|
|
|
for (i = 0; i < ranges.size(); i++)
|
|
{
|
|
unsigned sem_index = 0;
|
|
unsigned tex_sem_index = 0;
|
|
const std::string &name = compiler.get_member_name(
|
|
resource.base_type_id, ranges[i].index);
|
|
const spirv_cross::SPIRType &type = compiler.get_type(
|
|
compiler.get_type(resource.base_type_id).member_types[
|
|
ranges[i].index]);
|
|
slang_semantic sem = slang_uniform_name_to_semantic(
|
|
*reflection->semantic_map, name, &sem_index);
|
|
slang_texture_semantic tex_sem = slang_uniform_name_to_texture_semantic(
|
|
*reflection->texture_semantic_uniform_map,
|
|
name, &tex_sem_index);
|
|
|
|
if (tex_sem != SLANG_INVALID_TEXTURE_SEMANTIC &&
|
|
!validate_texture_semantic_index(reflection,
|
|
tex_sem, tex_sem_index))
|
|
return false;
|
|
|
|
if (sem != SLANG_INVALID_SEMANTIC)
|
|
{
|
|
if (!validate_type_for_semantic(type, sem))
|
|
{
|
|
RARCH_ERR("[Slang] Underlying type of semantic is invalid.\n");
|
|
return false;
|
|
}
|
|
|
|
switch (sem)
|
|
{
|
|
case SLANG_SEMANTIC_FLOAT_PARAMETER:
|
|
if (!set_ubo_float_parameter_offset(reflection, sem_index,
|
|
ranges[i].offset, type.vecsize, push_constant))
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
if (!set_ubo_offset(reflection, sem,
|
|
ranges[i].offset,
|
|
type.vecsize * type.columns, push_constant))
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
else if (tex_sem != SLANG_INVALID_TEXTURE_SEMANTIC)
|
|
{
|
|
if (!validate_type_for_texture_semantic(type))
|
|
{
|
|
RARCH_ERR("[Slang] Underlying type of texture"
|
|
" semantic is invalid.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!set_ubo_texture_offset(reflection, tex_sem, tex_sem_index,
|
|
ranges[i].offset, push_constant))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
RARCH_ERR("[Slang] Unknown semantic found: \"%s\".\n", name.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
slang_reflection::slang_reflection()
|
|
: ubo_size(0),
|
|
push_constant_size(0),
|
|
ubo_binding(0),
|
|
ubo_stage_mask(0),
|
|
push_constant_stage_mask(0),
|
|
texture_semantic_map(NULL),
|
|
texture_semantic_uniform_map(NULL),
|
|
semantic_map(NULL),
|
|
pass_number(0)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < SLANG_NUM_TEXTURE_SEMANTICS; i++)
|
|
semantic_textures[i].resize(
|
|
slang_texture_semantic_is_array(
|
|
static_cast<slang_texture_semantic>(i))
|
|
? 0 : 1);
|
|
}
|
|
|
|
bool slang_reflect(
|
|
const spirv_cross::Compiler &vertex_compiler,
|
|
const spirv_cross::Compiler &fragment_compiler,
|
|
const spirv_cross::ShaderResources &vertex,
|
|
const spirv_cross::ShaderResources &fragment,
|
|
slang_reflection *reflection)
|
|
{
|
|
uint32_t location_mask = 0;
|
|
uint32_t binding_mask = 0;
|
|
unsigned i = 0;
|
|
|
|
/* Validate use of unexpected types. */
|
|
if (
|
|
!vertex.sampled_images.empty()
|
|
|| !vertex.storage_buffers.empty()
|
|
|| !vertex.subpass_inputs.empty()
|
|
|| !vertex.storage_images.empty()
|
|
|| !vertex.atomic_counters.empty()
|
|
|| !fragment.storage_buffers.empty()
|
|
|| !fragment.subpass_inputs.empty()
|
|
|| !fragment.storage_images.empty()
|
|
|| !fragment.atomic_counters.empty())
|
|
{
|
|
RARCH_ERR("[Slang] Invalid resource type detected.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Validate vertex input. */
|
|
if (vertex.stage_inputs.size() != 2)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex must have two attributes.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fragment.stage_outputs.size() != 1)
|
|
{
|
|
RARCH_ERR("[Slang] Multiple render targets not supported.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fragment_compiler.get_decoration(
|
|
fragment.stage_outputs[0].id, spv::DecorationLocation) != 0)
|
|
{
|
|
RARCH_ERR("[Slang] Render target must use location = 0.\n");
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < vertex.stage_inputs.size(); i++)
|
|
location_mask |= 1 << vertex_compiler.get_decoration(
|
|
vertex.stage_inputs[i].id, spv::DecorationLocation);
|
|
|
|
if (location_mask != 0x3)
|
|
{
|
|
RARCH_ERR("[Slang] The two vertex attributes do not"
|
|
" use location = 0 and location = 1.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Validate the single uniform buffer. */
|
|
if (vertex.uniform_buffers.size() > 1)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex must use zero or one uniform buffer.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fragment.uniform_buffers.size() > 1)
|
|
{
|
|
RARCH_ERR("[Slang] Fragment must use zero or one uniform buffer.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Validate the single push constant buffer. */
|
|
if (vertex.push_constant_buffers.size() > 1)
|
|
{
|
|
RARCH_ERR("[Slang] Vertex must use zero or one push constant buffers.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fragment.push_constant_buffers.size() > 1)
|
|
{
|
|
RARCH_ERR("[Slang] Fragment must use zero or one push constant buffer.\n");
|
|
return false;
|
|
}
|
|
|
|
uint32_t vertex_ubo = vertex.uniform_buffers.empty()
|
|
? 0 : (uint32_t)vertex.uniform_buffers[0].id;
|
|
uint32_t fragment_ubo = fragment.uniform_buffers.empty()
|
|
? 0 : (uint32_t)fragment.uniform_buffers[0].id;
|
|
uint32_t vertex_push = vertex.push_constant_buffers.empty()
|
|
? 0 : (uint32_t)vertex.push_constant_buffers[0].id;
|
|
uint32_t fragment_push = fragment.push_constant_buffers.empty()
|
|
? 0 : (uint32_t)fragment.push_constant_buffers[0].id;
|
|
|
|
if (vertex_ubo &&
|
|
vertex_compiler.get_decoration(
|
|
vertex_ubo, spv::DecorationDescriptorSet) != 0)
|
|
{
|
|
RARCH_ERR("[Slang] Resources must use descriptor set #0.\n");
|
|
return false;
|
|
}
|
|
|
|
if (fragment_ubo &&
|
|
fragment_compiler.get_decoration(
|
|
fragment_ubo, spv::DecorationDescriptorSet) != 0)
|
|
{
|
|
RARCH_ERR("[Slang] Resources must use descriptor set #0.\n");
|
|
return false;
|
|
}
|
|
|
|
unsigned vertex_ubo_binding = vertex_ubo
|
|
? vertex_compiler.get_decoration(vertex_ubo, spv::DecorationBinding)
|
|
: (unsigned)-1;
|
|
unsigned fragment_ubo_binding = fragment_ubo
|
|
? fragment_compiler.get_decoration(fragment_ubo, spv::DecorationBinding)
|
|
: (unsigned)-1;
|
|
bool has_ubo = vertex_ubo || fragment_ubo;
|
|
|
|
if ( (vertex_ubo_binding != (unsigned)-1) &&
|
|
(fragment_ubo_binding != (unsigned)-1) &&
|
|
(vertex_ubo_binding != fragment_ubo_binding))
|
|
{
|
|
RARCH_ERR("[Slang] Vertex and fragment uniform buffer must have same binding.\n");
|
|
return false;
|
|
}
|
|
|
|
unsigned ubo_binding = (vertex_ubo_binding != (unsigned)-1)
|
|
? vertex_ubo_binding
|
|
: fragment_ubo_binding;
|
|
|
|
if (has_ubo && ubo_binding >= SLANG_NUM_BINDINGS)
|
|
{
|
|
RARCH_ERR("[Slang] Binding %u is out of range.\n", ubo_binding);
|
|
return false;
|
|
}
|
|
|
|
reflection->ubo_binding = has_ubo ? ubo_binding : 0;
|
|
reflection->ubo_stage_mask = 0;
|
|
reflection->ubo_size = 0;
|
|
reflection->push_constant_size = 0;
|
|
reflection->push_constant_stage_mask = 0;
|
|
|
|
if (vertex_ubo)
|
|
{
|
|
size_t _y;
|
|
reflection->ubo_stage_mask |= SLANG_STAGE_VERTEX_MASK;
|
|
_y = vertex_compiler.get_declared_struct_size(
|
|
vertex_compiler.get_type(
|
|
vertex.uniform_buffers[0].base_type_id));
|
|
reflection->ubo_size = MAX(reflection->ubo_size, _y);
|
|
}
|
|
|
|
if (fragment_ubo)
|
|
{
|
|
size_t _y;
|
|
reflection->ubo_stage_mask |= SLANG_STAGE_FRAGMENT_MASK;
|
|
_y = fragment_compiler.get_declared_struct_size(
|
|
fragment_compiler.get_type(
|
|
fragment.uniform_buffers[0].base_type_id));
|
|
reflection->ubo_size = MAX(reflection->ubo_size, _y);
|
|
}
|
|
|
|
if (vertex_push)
|
|
{
|
|
size_t _y;
|
|
reflection->push_constant_stage_mask |= SLANG_STAGE_VERTEX_MASK;
|
|
_y = vertex_compiler.get_declared_struct_size(
|
|
vertex_compiler.get_type(
|
|
vertex.push_constant_buffers[0].base_type_id));
|
|
reflection->push_constant_size = MAX(
|
|
reflection->push_constant_size, _y);
|
|
}
|
|
|
|
if (fragment_push)
|
|
{
|
|
size_t _y;
|
|
reflection->push_constant_stage_mask |= SLANG_STAGE_FRAGMENT_MASK;
|
|
_y = fragment_compiler.get_declared_struct_size(
|
|
fragment_compiler.get_type(
|
|
fragment.push_constant_buffers[0].base_type_id));
|
|
reflection->push_constant_size = MAX(
|
|
reflection->push_constant_size, _y);
|
|
}
|
|
|
|
/* Validate push constant size against Vulkan's
|
|
* minimum spec to avoid cross-vendor issues. */
|
|
if (reflection->push_constant_size > 128)
|
|
{
|
|
RARCH_ERR("[Slang] Exceeded maximum size of 128 bytes"
|
|
" for push constant buffer.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Find all relevant uniforms and push constants. */
|
|
if (vertex_ubo && !add_active_buffer_ranges(vertex_compiler,
|
|
vertex.uniform_buffers[0], reflection, false))
|
|
return false;
|
|
if (fragment_ubo && !add_active_buffer_ranges(fragment_compiler,
|
|
fragment.uniform_buffers[0], reflection, false))
|
|
return false;
|
|
if (vertex_push && !add_active_buffer_ranges(vertex_compiler,
|
|
vertex.push_constant_buffers[0], reflection, true))
|
|
return false;
|
|
if (fragment_push && !add_active_buffer_ranges(fragment_compiler,
|
|
fragment.push_constant_buffers[0], reflection, true))
|
|
return false;
|
|
|
|
if (has_ubo)
|
|
binding_mask = 1 << ubo_binding;
|
|
|
|
/* On to textures. */
|
|
for (i = 0; i < fragment.sampled_images.size(); i++)
|
|
{
|
|
unsigned array_index = 0;
|
|
unsigned set = fragment_compiler.get_decoration(
|
|
fragment.sampled_images[i].id,
|
|
spv::DecorationDescriptorSet);
|
|
unsigned binding = fragment_compiler.get_decoration(
|
|
fragment.sampled_images[i].id,
|
|
spv::DecorationBinding);
|
|
|
|
if (set != 0)
|
|
{
|
|
RARCH_ERR("[Slang] Resources must use descriptor set #0.\n");
|
|
return false;
|
|
}
|
|
|
|
if (binding >= SLANG_NUM_BINDINGS)
|
|
{
|
|
RARCH_ERR("[Slang] Binding %u is out of range.\n", ubo_binding);
|
|
return false;
|
|
}
|
|
|
|
if (binding_mask & (1 << binding))
|
|
{
|
|
RARCH_ERR("[Slang] Binding %u is already in use.\n", binding);
|
|
return false;
|
|
}
|
|
binding_mask |= 1 << binding;
|
|
|
|
slang_texture_semantic index = slang_name_to_texture_semantic(
|
|
*reflection->texture_semantic_map,
|
|
fragment.sampled_images[i].name, &array_index);
|
|
|
|
if (index == SLANG_INVALID_TEXTURE_SEMANTIC)
|
|
{
|
|
RARCH_ERR("[Slang] Texture name \"%s\" not found in semantic map, "
|
|
"Probably the texture name or pass alias is not defined "
|
|
"in the preset (Non-semantic textures not supported yet)\n",
|
|
fragment.sampled_images[i].name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!validate_texture_semantic_index(reflection, index, array_index))
|
|
return false;
|
|
|
|
resize_minimum(reflection->semantic_textures[index], array_index + 1);
|
|
slang_texture_semantic_meta &semantic =
|
|
reflection->semantic_textures[index][array_index];
|
|
semantic.binding = binding;
|
|
semantic.stage_mask = SLANG_STAGE_FRAGMENT_MASK;
|
|
semantic.texture = true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
RARCH_LOG("[Slang] Reflection\n");
|
|
RARCH_LOG("[Slang] Textures:\n");
|
|
|
|
for (i = 0; i < SLANG_NUM_TEXTURE_SEMANTICS; i++)
|
|
{
|
|
unsigned index = 0;
|
|
unsigned j;
|
|
for (j = 0; j < reflection->semantic_textures[i].size(); j++)
|
|
{
|
|
const slang_texture_semantic_meta &sem = reflection->semantic_textures[i][j];
|
|
if (sem.texture)
|
|
RARCH_LOG("[Slang] %s (#%u)\n",
|
|
texture_semantic_names[i], index);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
RARCH_LOG("[Slang] Uniforms (Vertex: %s, Fragment: %s):\n",
|
|
reflection->ubo_stage_mask & SLANG_STAGE_VERTEX_MASK ? "yes": "no",
|
|
reflection->ubo_stage_mask & SLANG_STAGE_FRAGMENT_MASK ? "yes": "no");
|
|
RARCH_LOG("[Slang] Push Constants (Vertex: %s, Fragment: %s):\n",
|
|
reflection->push_constant_stage_mask & SLANG_STAGE_VERTEX_MASK ? "yes": "no",
|
|
reflection->push_constant_stage_mask & SLANG_STAGE_FRAGMENT_MASK ? "yes": "no");
|
|
|
|
for (i = 0; i < SLANG_NUM_SEMANTICS; i++)
|
|
{
|
|
if (reflection->semantics[i].uniform)
|
|
{
|
|
RARCH_LOG("[Slang] %s (Offset: %u)\n",
|
|
semantic_uniform_names[i],
|
|
unsigned(reflection->semantics[i].ubo_offset));
|
|
}
|
|
|
|
if (reflection->semantics[i].push_constant)
|
|
{
|
|
RARCH_LOG("[Slang] %s (PushOffset: %u)\n",
|
|
semantic_uniform_names[i],
|
|
unsigned(reflection->semantics[i].push_constant_offset));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < SLANG_NUM_TEXTURE_SEMANTICS; i++)
|
|
{
|
|
unsigned index = 0;
|
|
unsigned j;
|
|
for (j = 0; j < reflection->semantic_textures[i].size(); j++)
|
|
{
|
|
const slang_texture_semantic_meta &sem = reflection->semantic_textures[i][j];
|
|
if (sem.uniform)
|
|
{
|
|
RARCH_LOG("[Slang] %s (#%u) (Offset: %u)\n",
|
|
texture_semantic_uniform_names[i],
|
|
index,
|
|
unsigned(sem.ubo_offset));
|
|
}
|
|
|
|
if (sem.push_constant)
|
|
{
|
|
RARCH_LOG("[Slang] %s (#%u) (PushOffset: %u)\n",
|
|
texture_semantic_uniform_names[i],
|
|
index,
|
|
unsigned(sem.push_constant_offset));
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
RARCH_LOG("[Slang] Parameters:\n");
|
|
|
|
for (i = 0; i < reflection->semantic_float_parameters.size(); i++)
|
|
{
|
|
const slang_semantic_meta ¶m = reflection->semantic_float_parameters[i];
|
|
|
|
if (param.uniform)
|
|
RARCH_LOG("[Slang] #%u (Offset: %u)\n", i,
|
|
(unsigned int)param.ubo_offset);
|
|
if (param.push_constant)
|
|
RARCH_LOG("[Slang] #%u (PushOffset: %u)\n", i,
|
|
(unsigned int)param.push_constant_offset);
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool slang_reflect_spirv(const std::vector<uint32_t> &vertex,
|
|
const std::vector<uint32_t> &fragment,
|
|
slang_reflection *reflection)
|
|
{
|
|
try
|
|
{
|
|
spirv_cross::Compiler vertex_compiler(vertex);
|
|
spirv_cross::Compiler fragment_compiler(fragment);
|
|
spirv_cross::ShaderResources
|
|
vertex_resources = vertex_compiler.get_shader_resources();
|
|
spirv_cross::ShaderResources
|
|
fragment_resources = fragment_compiler.get_shader_resources();
|
|
|
|
if (!slang_reflect(vertex_compiler, fragment_compiler,
|
|
vertex_resources, fragment_resources,
|
|
reflection))
|
|
{
|
|
RARCH_ERR("[Slang] Failed to reflect SPIR-V."
|
|
" Resource usage is inconsistent with expectations.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
RARCH_ERR("[Slang] SPIRV-Cross threw exception: %s.\n", e.what());
|
|
return false;
|
|
}
|
|
}
|