Skip to content

Commit 2020a2e

Browse files
Add aggressive shader caching to slang shader backend (#18933)
* add aggressive caching to slang shader backend * fix griffin builds * switch to libretro-common hash method to avoid mbedtls dep for MSVC/UWP buildfix * switch to libretro-common functions when possible --------- Co-authored-by: hunterk <[email protected]>
1 parent cc1a131 commit 2020a2e

5 files changed

Lines changed: 421 additions & 0 deletions

File tree

Makefile.common

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,7 @@ ifeq ($(HAVE_SLANG),1)
18821882
DEFINES += -DHAVE_SLANG
18831883
OBJ += gfx/drivers_shader/slang_process.o
18841884
OBJ += gfx/drivers_shader/glslang_util.o
1885+
OBJ += gfx/drivers_shader/slang_cache.o
18851886
endif
18861887

18871888
ifeq ($(HAVE_SHADERS_COMMON), 1)

gfx/drivers_shader/slang_cache.cpp

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
#include "slang_cache.h"
2+
#include "glslang_util.h"
3+
#include "slang_process.h"
4+
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <file/file_path.h>
8+
#include <streams/file_stream.h>
9+
#include <vfs/vfs.h>
10+
#include <compat/strl.h>
11+
#include <lrc_hash.h>
12+
13+
#include "../../configuration.h"
14+
#include "../../verbosity.h"
15+
16+
#define SPIRV_CACHE_VERSION 1
17+
#define SPIRV_CACHE_SUBDIR "spirv"
18+
19+
/**
20+
* Get the full path to the SPIR-V cache directory
21+
*
22+
* @param cache_dir_out Output buffer for the cache directory path (must be at least PATH_MAX_LENGTH)
23+
* @param cache_dir_out_len Size of the output buffer
24+
* @return true on success, false if cache dir is not configured
25+
*/
26+
static bool spirv_cache_get_dir(char *cache_dir_out, size_t cache_dir_out_len)
27+
{
28+
settings_t *settings = config_get_ptr();
29+
30+
if (!settings || !settings->paths.directory_cache[0])
31+
return false;
32+
33+
/* Build the spirv subdirectory path */
34+
snprintf(cache_dir_out, cache_dir_out_len, "%s/%s",
35+
settings->paths.directory_cache, SPIRV_CACHE_SUBDIR);
36+
37+
return true;
38+
}
39+
40+
/**
41+
* Ensure the SPIR-V cache directory exists
42+
*
43+
* @return true if directory exists or was created, false on error
44+
*/
45+
static bool spirv_cache_ensure_dir(void)
46+
{
47+
char cache_dir[PATH_MAX_LENGTH];
48+
49+
if (!spirv_cache_get_dir(cache_dir, sizeof(cache_dir)))
50+
return false;
51+
52+
return path_mkdir(cache_dir);
53+
}
54+
55+
/**
56+
* Get the full path to a cache file for a given hash
57+
*
58+
* @param hash Hash string (64 characters)
59+
* @param cache_file_out Output buffer for the full cache file path
60+
* @param cache_file_out_len Size of the output buffer
61+
* @return true on success, false on error
62+
*/
63+
static bool spirv_cache_get_filename(const char *hash,
64+
char *cache_file_out, size_t cache_file_out_len)
65+
{
66+
char cache_dir[PATH_MAX_LENGTH];
67+
68+
if (!spirv_cache_get_dir(cache_dir, sizeof(cache_dir)))
69+
return false;
70+
71+
snprintf(cache_file_out, cache_file_out_len, "%s/%s.spirv",
72+
cache_dir, hash);
73+
74+
return true;
75+
}
76+
77+
/**
78+
* Write a null-terminated string to a file with length prefix
79+
*
80+
* @param file File pointer (opened in binary mode)
81+
* @param str String to write (may be NULL or empty)
82+
* @return true on success, false on error
83+
*/
84+
static bool spirv_cache_write_string(RFILE *file, const std::string &str)
85+
{
86+
uint32_t len = str.length();
87+
88+
if (filestream_write(file, &len, sizeof(uint32_t)) != sizeof(uint32_t))
89+
return false;
90+
91+
if (len > 0 && filestream_write(file, str.c_str(), len) != len)
92+
return false;
93+
94+
return true;
95+
}
96+
97+
/**
98+
* Read a null-terminated string from a file
99+
*
100+
* @param file File pointer (opened in binary mode)
101+
* @param str_out Output string
102+
* @return true on success, false on error
103+
*/
104+
static bool spirv_cache_read_string(RFILE *file, std::string &str_out)
105+
{
106+
uint32_t len;
107+
108+
if (filestream_read(file, &len, sizeof(uint32_t)) != sizeof(uint32_t))
109+
return false;
110+
111+
if (len == 0)
112+
{
113+
str_out.clear();
114+
return true;
115+
}
116+
117+
/* Allocate and read string */
118+
char *buf = new char[len + 1];
119+
if (!buf)
120+
return false;
121+
122+
if (filestream_read(file, buf, len) != len)
123+
{
124+
delete[] buf;
125+
return false;
126+
}
127+
128+
buf[len] = '\0';
129+
str_out = buf;
130+
delete[] buf;
131+
132+
return true;
133+
}
134+
135+
extern "C" {
136+
137+
bool spirv_cache_compute_hash(const char *vertex_source, const char *fragment_source,
138+
char *hash_out)
139+
{
140+
if (!vertex_source || !fragment_source || !hash_out)
141+
return false;
142+
143+
/* Build combined hash input: vertex + "|" + fragment */
144+
size_t vertex_len = strlen(vertex_source);
145+
size_t fragment_len = strlen(fragment_source);
146+
size_t total_len = vertex_len + 1 + fragment_len; /* 1 for "|" separator */
147+
148+
uint8_t *combined = new uint8_t[total_len];
149+
if (!combined)
150+
return false;
151+
152+
memcpy(combined, vertex_source, vertex_len);
153+
combined[vertex_len] = '|';
154+
memcpy(combined + vertex_len + 1, fragment_source, fragment_len);
155+
156+
/* Compute SHA256 hash using libretro-common */
157+
sha256_hash(hash_out, combined, total_len);
158+
159+
delete[] combined;
160+
return true;
161+
}
162+
163+
bool spirv_cache_load(const char *hash, struct glslang_output *output)
164+
{
165+
RFILE *file;
166+
char cache_file[PATH_MAX_LENGTH];
167+
uint8_t version;
168+
uint32_t vertex_size, fragment_size, param_count, i;
169+
uint16_t rt_format;
170+
171+
if (!hash || !output)
172+
return false;
173+
174+
if (!spirv_cache_get_filename(hash, cache_file, sizeof(cache_file)))
175+
return false;
176+
177+
file = filestream_open(cache_file, RETRO_VFS_FILE_ACCESS_READ,
178+
RETRO_VFS_FILE_ACCESS_HINT_NONE);
179+
if (!file)
180+
return false; /* Cache file doesn't exist yet */
181+
182+
/* Read version */
183+
if (filestream_read(file, &version, sizeof(uint8_t)) != sizeof(uint8_t))
184+
goto error;
185+
186+
if (version != SPIRV_CACHE_VERSION)
187+
goto error; /* Version mismatch */
188+
189+
/* Read vertex SPIR-V */
190+
if (filestream_read(file, &vertex_size, sizeof(uint32_t)) != sizeof(uint32_t))
191+
goto error;
192+
193+
if (vertex_size > 0)
194+
{
195+
output->vertex.resize(vertex_size);
196+
if (filestream_read(file, output->vertex.data(), vertex_size * sizeof(uint32_t)) != (int64_t)(vertex_size * sizeof(uint32_t)))
197+
goto error;
198+
}
199+
200+
/* Read fragment SPIR-V */
201+
if (filestream_read(file, &fragment_size, sizeof(uint32_t)) != sizeof(uint32_t))
202+
goto error;
203+
204+
if (fragment_size > 0)
205+
{
206+
output->fragment.resize(fragment_size);
207+
if (filestream_read(file, output->fragment.data(), fragment_size * sizeof(uint32_t)) != (int64_t)(fragment_size * sizeof(uint32_t)))
208+
goto error;
209+
}
210+
211+
/* Read parameters count */
212+
if (filestream_read(file, &param_count, sizeof(uint32_t)) != sizeof(uint32_t))
213+
goto error;
214+
215+
if (param_count > 0)
216+
output->meta.parameters.resize(param_count);
217+
218+
/* Read each parameter */
219+
for (i = 0; i < param_count; i++)
220+
{
221+
glslang_parameter &param = output->meta.parameters[i];
222+
223+
if (!spirv_cache_read_string(file, param.id))
224+
goto error;
225+
226+
if (!spirv_cache_read_string(file, param.desc))
227+
goto error;
228+
229+
if (filestream_read(file, &param.initial, sizeof(float)) != sizeof(float))
230+
goto error;
231+
if (filestream_read(file, &param.minimum, sizeof(float)) != sizeof(float))
232+
goto error;
233+
if (filestream_read(file, &param.maximum, sizeof(float)) != sizeof(float))
234+
goto error;
235+
if (filestream_read(file, &param.step, sizeof(float)) != sizeof(float))
236+
goto error;
237+
}
238+
239+
/* Read shader name */
240+
if (!spirv_cache_read_string(file, output->meta.name))
241+
goto error;
242+
243+
/* Read render target format */
244+
if (filestream_read(file, &rt_format, sizeof(uint16_t)) != sizeof(uint16_t))
245+
goto error;
246+
output->meta.rt_format = (enum glslang_format)rt_format;
247+
248+
filestream_close(file);
249+
250+
RARCH_LOG("[Slang Cache] Loaded shader cache for hash: %.16s...\n", hash);
251+
252+
return true;
253+
254+
error:
255+
filestream_close(file);
256+
return false;
257+
}
258+
259+
bool spirv_cache_save(const char *hash, const struct glslang_output *output)
260+
{
261+
RFILE *file;
262+
char cache_file[PATH_MAX_LENGTH];
263+
uint8_t version = SPIRV_CACHE_VERSION;
264+
uint32_t vertex_size, fragment_size, param_count, i;
265+
uint16_t rt_format;
266+
267+
if (!hash || !output)
268+
return false;
269+
270+
/* Ensure cache directory exists */
271+
if (!spirv_cache_ensure_dir())
272+
return false;
273+
274+
if (!spirv_cache_get_filename(hash, cache_file, sizeof(cache_file)))
275+
return false;
276+
277+
file = filestream_open(cache_file, RETRO_VFS_FILE_ACCESS_WRITE,
278+
RETRO_VFS_FILE_ACCESS_HINT_NONE);
279+
if (!file)
280+
return false;
281+
282+
/* Write version */
283+
if (filestream_write(file, &version, sizeof(uint8_t)) != sizeof(uint8_t))
284+
goto error;
285+
286+
/* Write vertex SPIR-V */
287+
vertex_size = output->vertex.size();
288+
if (filestream_write(file, &vertex_size, sizeof(uint32_t)) != sizeof(uint32_t))
289+
goto error;
290+
if (vertex_size > 0)
291+
{
292+
if (filestream_write(file, output->vertex.data(), vertex_size * sizeof(uint32_t)) != (int64_t)(vertex_size * sizeof(uint32_t)))
293+
goto error;
294+
}
295+
296+
/* Write fragment SPIR-V */
297+
fragment_size = output->fragment.size();
298+
if (filestream_write(file, &fragment_size, sizeof(uint32_t)) != sizeof(uint32_t))
299+
goto error;
300+
if (fragment_size > 0)
301+
{
302+
if (filestream_write(file, output->fragment.data(), fragment_size * sizeof(uint32_t)) != (int64_t)(fragment_size * sizeof(uint32_t)))
303+
goto error;
304+
}
305+
306+
/* Write parameters */
307+
param_count = output->meta.parameters.size();
308+
if (filestream_write(file, &param_count, sizeof(uint32_t)) != sizeof(uint32_t))
309+
goto error;
310+
311+
for (i = 0; i < param_count; i++)
312+
{
313+
const glslang_parameter &param = output->meta.parameters[i];
314+
315+
if (!spirv_cache_write_string(file, param.id))
316+
goto error;
317+
if (!spirv_cache_write_string(file, param.desc))
318+
goto error;
319+
320+
if (filestream_write(file, &param.initial, sizeof(float)) != sizeof(float))
321+
goto error;
322+
if (filestream_write(file, &param.minimum, sizeof(float)) != sizeof(float))
323+
goto error;
324+
if (filestream_write(file, &param.maximum, sizeof(float)) != sizeof(float))
325+
goto error;
326+
if (filestream_write(file, &param.step, sizeof(float)) != sizeof(float))
327+
goto error;
328+
}
329+
330+
/* Write shader name */
331+
if (!spirv_cache_write_string(file, output->meta.name))
332+
goto error;
333+
334+
/* Write render target format */
335+
rt_format = (uint16_t)output->meta.rt_format;
336+
if (filestream_write(file, &rt_format, sizeof(uint16_t)) != sizeof(uint16_t))
337+
goto error;
338+
339+
filestream_close(file);
340+
341+
RARCH_LOG("[Slang Cache] Saved shader cache for hash: %.16s...\n", hash);
342+
343+
return true;
344+
345+
error:
346+
filestream_close(file);
347+
filestream_delete(cache_file); /* Clean up partial file on error */
348+
return false;
349+
}
350+
351+
} /* extern "C" */

0 commit comments

Comments
 (0)