Blender V5.0
gl_compilation_subprocess.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#if BLI_SUBPROCESS_SUPPORT
8
9# include "BKE_appdir.hh"
10# include "BLI_fileops.hh"
11# include "BLI_hash.hh"
12# include "BLI_path_utils.hh"
13# include "BLI_string.h"
14# include "BLI_threads.h"
15# include "CLG_log.h"
16# include "GHOST_C-api.h"
17# include "GPU_context.hh"
18# include "GPU_init_exit.hh"
20# include <iostream>
21# include <string>
22
23# ifndef _WIN32
24# include <unistd.h>
25# else
26# include "BLI_winstuff.h"
27# endif
28
29/* Include after `BLI_winstuff.h` to avoid APIENTRY redefinition. */
30# include <epoxy/gl.h>
31
32namespace blender::gpu {
33
34class SubprocessShader {
35 GLuint comp_ = 0;
36 GLuint vert_ = 0;
37 GLuint geom_ = 0;
38 GLuint frag_ = 0;
39 GLuint program_ = 0;
40 bool success_ = false;
41
42 public:
43 SubprocessShader(const char *comp_src,
44 const char *vert_src,
45 const char *geom_src,
46 const char *frag_src)
47 {
48 GLint status;
49 program_ = glCreateProgram();
50
51 auto compile_stage = [&](const char *src, GLenum stage) -> GLuint {
52 if (src == nullptr) {
53 /* We only want status errors if compilation fails. */
54 status = GL_TRUE;
55 return 0;
56 }
57
58 GLuint shader = glCreateShader(stage);
59 glShaderSource(shader, 1, &src, nullptr);
60 glCompileShader(shader);
61 glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
62 glAttachShader(program_, shader);
63 return shader;
64 };
65
66 comp_ = compile_stage(comp_src, GL_COMPUTE_SHADER);
67 if (!status) {
68 return;
69 }
70
71 vert_ = compile_stage(vert_src, GL_VERTEX_SHADER);
72 if (!status) {
73 return;
74 }
75
76 geom_ = compile_stage(geom_src, GL_GEOMETRY_SHADER);
77 if (!status) {
78 return;
79 }
80
81 frag_ = compile_stage(frag_src, GL_FRAGMENT_SHADER);
82 if (!status) {
83 return;
84 }
85
86 glLinkProgram(program_);
87 glGetProgramiv(program_, GL_LINK_STATUS, &status);
88 if (!status) {
89 return;
90 }
91
92 success_ = true;
93 }
94
95 ~SubprocessShader()
96 {
97 glDeleteShader(comp_);
98 glDeleteShader(vert_);
99 glDeleteShader(geom_);
100 glDeleteShader(frag_);
101 glDeleteProgram(program_);
102 }
103
104 ShaderBinaryHeader *get_binary(void *memory)
105 {
106 ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(memory);
107 bin->format = 0;
108 bin->size = 0;
109
110 if (success_) {
111 glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &bin->size);
112 if (bin->size > sizeof(ShaderBinaryHeader::data)) {
113 bin->size = 0;
114 return nullptr;
115 }
116 glGetProgramBinary(program_, bin->size, nullptr, &bin->format, bin->data);
117 }
118
119 return bin;
120 }
121};
122
123/* Check if the binary is valid and can be loaded by the driver. */
124static bool validate_binary(void *binary)
125{
126 ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(binary);
127 GLuint program = glCreateProgram();
128 glProgramBinary(program, bin->format, bin->data, bin->size);
129 GLint status;
130 glGetProgramiv(program, GL_LINK_STATUS, &status);
131 glDeleteProgram(program);
132 return status;
133}
134
135std::string GL_shader_cache_dir_get()
136{
137 static char tmp_dir_buffer[1024];
138 BKE_appdir_folder_caches(tmp_dir_buffer, sizeof(tmp_dir_buffer));
139
140 std::string cache_dir = std::string(tmp_dir_buffer) + "gl-shader-cache" + SEP_STR;
141 BLI_dir_create_recursive(cache_dir.c_str());
142
143 return cache_dir;
144}
145
146} // namespace blender::gpu
147
148void GPU_compilation_subprocess_run(const char *subprocess_name)
149{
150 using namespace blender;
151 using namespace blender::gpu;
152
153# ifndef _WIN32
155 pid_t ppid = getppid();
156# endif
157
158 CLG_init();
160
161 /* Prevent the ShaderCompiler from spawning extra threads/contexts, we don't need them. */
162 GCaps.use_main_context_workaround = true;
163
164 std::string name = subprocess_name;
165 SharedMemory shared_mem(name, compilation_subprocess_shared_memory_size, false);
166 if (!shared_mem.get_data()) {
167 std::cerr << "Compilation Subprocess: Failed to open shared memory " << subprocess_name
168 << "\n";
169 return;
170 }
171 SharedSemaphore start_semaphore(name + "_START", true);
172 SharedSemaphore end_semaphore(name + "_END", true);
173 SharedSemaphore close_semaphore(name + "_CLOSE", true);
174
175 GHOST_SystemHandle ghost_system = GHOST_CreateSystemBackground();
178 GHOST_GPUSettings gpu_settings = {0};
179 gpu_settings.context_type = GHOST_kDrawingContextTypeOpenGL;
180 GHOST_ContextHandle ghost_context = GHOST_CreateGPUContext(ghost_system, gpu_settings);
181 if (ghost_context == nullptr) {
182 std::cerr << "Compilation Subprocess: Failed to initialize GHOST context for "
183 << subprocess_name << "\n";
185 return;
186 }
187 GHOST_ActivateGPUContext(ghost_context);
188 GPUContext *gpu_context = GPU_context_create(nullptr, ghost_context);
189 GPU_init();
190
191 std::string cache_dir = GL_shader_cache_dir_get();
192
193 while (true) {
194 /* Process events to avoid crashes on Wayland.
195 * See https://bugreports.qt.io/browse/QTBUG-81504 */
197
198# ifdef _WIN32
199 start_semaphore.decrement();
200# else
201 bool lost_parent = false;
202 while (!lost_parent && !start_semaphore.try_decrement(1000)) {
203 lost_parent = getppid() != ppid;
204 }
205 if (lost_parent) {
206 std::cerr << "Compilation Subprocess: Lost parent process\n";
207 break;
208 }
209# endif
210
211 if (close_semaphore.try_decrement()) {
212 break;
213 }
214
215 ShaderSourceHeader *source = reinterpret_cast<ShaderSourceHeader *>(shared_mem.get_data());
216 const char *next_src = source->sources;
217 const char *comp_src = nullptr;
218 const char *vert_src = nullptr;
219 const char *geom_src = nullptr;
220 const char *frag_src = nullptr;
221
223 std::string hash_str = "_";
224
225 auto get_src = [&]() {
226 const char *src = next_src;
227 next_src += strlen(src) + sizeof('\0');
228 hash_str += std::to_string(hasher(src)) + "_";
229 return src;
230 };
231
232 if (source->type == ShaderSourceHeader::Type::COMPUTE) {
233 comp_src = get_src();
234 }
235 else {
236 vert_src = get_src();
237 if (source->type == ShaderSourceHeader::Type::GRAPHICS_WITH_GEOMETRY_STAGE) {
238 geom_src = get_src();
239 }
240 frag_src = get_src();
241 }
242
243 std::string cache_path = cache_dir + SEP_STR + hash_str;
244
245 /* TODO: This should lock the files? */
246 if (BLI_exists(cache_path.c_str())) {
247 {
248 /* Store the source hash in the shared memory.
249 * If the subprocess crashes, the main process will delete the cache file. */
250 std::string source_hash = "SOURCE_HASH:" + hash_str;
251 BLI_strncpy(reinterpret_cast<char *>(shared_mem.get_data()),
252 source_hash.c_str(),
253 source_hash.size() + 1);
254 }
255 /* Prevent old cache files from being deleted if they're still being used. */
256 BLI_file_touch(cache_path.c_str());
257 /* Read cached binary. */
258 fstream file(cache_path, std::ios::binary | std::ios::in | std::ios::ate);
259 std::streamsize size = file.tellg();
260 if (size <= compilation_subprocess_shared_memory_size) {
261 file.seekg(0, std::ios::beg);
262 /* Use temp memory so we don't overwrite the source hash. */
263 static char tmp_mem[compilation_subprocess_shared_memory_size];
264 file.read(tmp_mem, size);
265 /* Close first in case validation hangs the driver. */
266 file.close();
267 /* Ensure it's valid. */
268 if (!validate_binary(tmp_mem)) {
269 std::cout << "Compilation Subprocess: Failed to load cached shader binary " << hash_str
270 << "\n";
271 /* TODO: No longer true. */
272 /* We can't compile the shader anymore since we have written over the source code,
273 * but we delete the cache for the next time this shader is requested. */
274 BLI_delete(cache_path.c_str(), false, false);
275 }
276 /* Copy the temp memory to the shared memory now that we know loading the shader doesn't
277 * crash the driver. */
278 memcpy(shared_mem.get_data(), tmp_mem, size);
279 end_semaphore.increment();
280 continue;
281 }
282 else {
283 /* This should never happen, since shaders larger than the pool size should be discarded
284 * and compiled in the main Blender process. */
285 std::cerr << "Compilation Subprocess: Wrong size for cached shader binary " << hash_str
286 << "\n";
288 }
289 }
290
291 SubprocessShader shader(comp_src, vert_src, geom_src, frag_src);
292 ShaderBinaryHeader *binary = shader.get_binary(shared_mem.get_data());
293
294 if (binary) {
295 fstream file(cache_path, std::ios::binary | std::ios::out);
296 file.write(reinterpret_cast<char *>(shared_mem.get_data()),
297 binary->size + offsetof(ShaderBinaryHeader, data));
298 }
299
300 end_semaphore.increment();
301 }
302
303 GPU_exit();
304 GPU_context_discard(gpu_context);
307}
308
309namespace blender::gpu {
310void GL_shader_cache_dir_clear_old()
311{
312 std::string cache_dir = GL_shader_cache_dir_get();
313
314 direntry *entries = nullptr;
315 uint32_t dir_len = BLI_filelist_dir_contents(cache_dir.c_str(), &entries);
316 for (int i : blender::IndexRange(dir_len)) {
317 direntry entry = entries[i];
318 if (S_ISDIR(entry.s.st_mode)) {
319 continue;
320 }
321 const time_t ts_now = time(nullptr);
322 const time_t delete_threshold = 60 /*seconds*/ * 60 /*minutes*/ * 24 /*hours*/ * 30 /*days*/;
323 if (entry.s.st_mtime + delete_threshold < ts_now) {
324 BLI_delete(entry.path, false, false);
325 }
326 }
327 BLI_filelist_free(entries, dir_len);
328}
329} // namespace blender::gpu
330
331#endif
bool BKE_appdir_folder_caches(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
Definition appdir.cc:203
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
bool BLI_file_touch(const char *filepath) ATTR_NONNULL(1)
Definition fileops_c.cc:316
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:414
unsigned int BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist)
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
void BLI_filelist_free(struct direntry *filelist, unsigned int nrentries)
File and directory operations.
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
void BLI_threadapi_init(void)
Definition threads.cc:114
Compatibility-like things for windows.
#define S_ISDIR(x)
void CLG_init()
Definition clog.cc:873
GHOST C-API function and type declarations.
GHOST_ContextHandle GHOST_CreateGPUContext(GHOST_SystemHandle systemhandle, GHOST_GPUSettings gpu_settings)
GHOST_SystemHandle GHOST_CreateSystemBackground(void)
bool GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, bool waitForEvent)
GHOST_TSuccess GHOST_ActivateGPUContext(GHOST_ContextHandle contexthandle)
GHOST_TSuccess GHOST_DisposeGPUContext(GHOST_SystemHandle systemhandle, GHOST_ContextHandle contexthandle)
GHOST_TSuccess GHOST_DisposeSystem(GHOST_SystemHandle systemhandle)
static GHOST_SystemCocoa * ghost_system
GPUContext * GPU_context_create(void *ghost_window, void *ghost_context)
void GPU_context_discard(GPUContext *)
void GPU_backend_ghost_system_set(void *ghost_system_handle)
void GPU_init()
void GPU_exit()
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
#define offsetof(t, d)
GPUCapabilities GCaps
const char * name
const int status
GHOST_TDrawingContextType context_type
struct stat s
const char * path
i
Definition text_draw.cc:230
#define SEP_STR
Definition unit.cc:39