Files
RetroArch/gfx/drivers_shader/slang_process.cpp
T
LibretroAdmin b9be38fd55 strl: copy string literals without scanning them at runtime
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).
2026-08-30 02:10:20 +02:00

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 &param = 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;
}
}