Blender V4.3
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 "CLG_log.h"
14# include "GHOST_C-api.h"
15# include "GPU_context.hh"
16# include "GPU_init_exit.hh"
17# include <iostream>
18# include <string>
19
20# ifndef _WIN32
21# include <unistd.h>
22# else
23# include "BLI_winstuff.h"
24# endif
25
26/* Include after BLI_winstuff.h to avoid APIENTRY redefinition. */
27# include <epoxy/gl.h>
28
29namespace blender::gpu {
30
31class SubprocessShader {
32 GLuint comp_ = 0;
33 GLuint vert_ = 0;
34 GLuint geom_ = 0;
35 GLuint frag_ = 0;
36 GLuint program_ = 0;
37 bool success_ = false;
38
39 public:
40 SubprocessShader(const char *comp_src,
41 const char *vert_src,
42 const char *geom_src,
43 const char *frag_src)
44 {
45 GLint status;
46 program_ = glCreateProgram();
47
48 auto compile_stage = [&](const char *src, GLenum stage) -> GLuint {
49 if (src == nullptr) {
50 /* We only want status errors if compilation fails. */
51 status = GL_TRUE;
52 return 0;
53 }
54
55 GLuint shader = glCreateShader(stage);
56 glShaderSource(shader, 1, &src, nullptr);
57 glCompileShader(shader);
58 glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
59 glAttachShader(program_, shader);
60 return shader;
61 };
62
63 comp_ = compile_stage(comp_src, GL_COMPUTE_SHADER);
64 if (!status) {
65 return;
66 }
67
68 vert_ = compile_stage(vert_src, GL_VERTEX_SHADER);
69 if (!status) {
70 return;
71 }
72
73 geom_ = compile_stage(geom_src, GL_GEOMETRY_SHADER);
74 if (!status) {
75 return;
76 }
77
78 frag_ = compile_stage(frag_src, GL_FRAGMENT_SHADER);
79 if (!status) {
80 return;
81 }
82
83 glLinkProgram(program_);
84 glGetProgramiv(program_, GL_LINK_STATUS, &status);
85 if (!status) {
86 return;
87 }
88
89 success_ = true;
90 }
91
92 ~SubprocessShader()
93 {
94 glDeleteShader(comp_);
95 glDeleteShader(vert_);
96 glDeleteShader(geom_);
97 glDeleteShader(frag_);
98 glDeleteProgram(program_);
99 }
100
101 ShaderBinaryHeader *get_binary(void *memory)
102 {
103 ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(memory);
104 bin->format = 0;
105 bin->size = 0;
106
107 if (success_) {
108 glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &bin->size);
109 if (bin->size > sizeof(ShaderBinaryHeader::data)) {
110 bin->size = 0;
111 return nullptr;
112 }
113 glGetProgramBinary(program_, bin->size, nullptr, &bin->format, bin->data);
114 }
115
116 return bin;
117 }
118};
119
120/* Check if the binary is valid and can be loaded by the driver. */
121static bool validate_binary(void *binary)
122{
123 ShaderBinaryHeader *bin = reinterpret_cast<ShaderBinaryHeader *>(binary);
124 GLuint program = glCreateProgram();
125 glProgramBinary(program, bin->format, bin->data, bin->size);
126 GLint status;
127 glGetProgramiv(program, GL_LINK_STATUS, &status);
128 glDeleteProgram(program);
129 return status;
130}
131
132} // namespace blender::gpu
133
134static std::string cache_dir_get()
135{
136 static char tmp_dir_buffer[1024];
137 BKE_appdir_folder_caches(tmp_dir_buffer, sizeof(tmp_dir_buffer));
138
139 std::string cache_dir = std::string(tmp_dir_buffer) + "gl-shader-cache" + SEP_STR;
140 BLI_dir_create_recursive(cache_dir.c_str());
141
142 return cache_dir;
143}
144
145void GPU_compilation_subprocess_run(const char *subprocess_name)
146{
147 using namespace blender;
148 using namespace blender::gpu;
149
150# ifndef _WIN32
152 pid_t ppid = getppid();
153# endif
154
155 CLG_init();
156
157 std::string name = subprocess_name;
158 SharedMemory shared_mem(name, compilation_subprocess_shared_memory_size, false);
159 if (!shared_mem.get_data()) {
160 std::cerr << "Compilation Subprocess: Failed to open shared memory " << subprocess_name
161 << "\n";
162 return;
163 }
164 SharedSemaphore start_semaphore(name + "_START", true);
165 SharedSemaphore end_semaphore(name + "_END", true);
166 SharedSemaphore close_semaphore(name + "_CLOSE", true);
167
168 GHOST_SystemHandle ghost_system = GHOST_CreateSystemBackground();
171 GHOST_GPUSettings gpu_settings = {0};
172 gpu_settings.context_type = GHOST_kDrawingContextTypeOpenGL;
173 GHOST_ContextHandle ghost_context = GHOST_CreateGPUContext(ghost_system, gpu_settings);
174 if (ghost_context == nullptr) {
175 std::cerr << "Compilation Subprocess: Failed to initialize GHOST context for "
176 << subprocess_name << "\n";
178 return;
179 }
180 GHOST_ActivateGPUContext(ghost_context);
181 GPUContext *gpu_context = GPU_context_create(nullptr, ghost_context);
182 GPU_init();
183
184 std::string cache_dir = cache_dir_get();
185
186 while (true) {
187 /* Process events to avoid crashes on Wayland.
188 * See https://bugreports.qt.io/browse/QTBUG-81504 */
190
191# ifdef _WIN32
192 start_semaphore.decrement();
193# else
194 bool lost_parent = false;
195 while (!lost_parent && !start_semaphore.try_decrement(1000)) {
196 lost_parent = getppid() != ppid;
197 }
198 if (lost_parent) {
199 std::cerr << "Compilation Subprocess: Lost parent process\n";
200 break;
201 }
202# endif
203
204 if (close_semaphore.try_decrement()) {
205 break;
206 }
207
208 ShaderSourceHeader *source = reinterpret_cast<ShaderSourceHeader *>(shared_mem.get_data());
209 const char *next_src = source->sources;
210 const char *comp_src = nullptr;
211 const char *vert_src = nullptr;
212 const char *geom_src = nullptr;
213 const char *frag_src = nullptr;
214
216 std::string hash_str = "_";
217
218 auto get_src = [&]() {
219 const char *src = next_src;
220 next_src += strlen(src) + sizeof('\0');
221 hash_str += std::to_string(hasher(src)) + "_";
222 return src;
223 };
224
225 if (source->type == ShaderSourceHeader::Type::COMPUTE) {
226 comp_src = get_src();
227 }
228 else {
229 vert_src = get_src();
230 if (source->type == ShaderSourceHeader::Type::GRAPHICS_WITH_GEOMETRY_STAGE) {
231 geom_src = get_src();
232 }
233 frag_src = get_src();
234 }
235
236 std::string cache_path = cache_dir + SEP_STR + hash_str;
237
238 /* TODO: This should lock the files? */
239 if (BLI_exists(cache_path.c_str())) {
240 /* Prevent old cache files from being deleted if they're still being used. */
241 BLI_file_touch(cache_path.c_str());
242 /* Read cached binary. */
243 fstream file(cache_path, std::ios::binary | std::ios::in | std::ios::ate);
244 std::streamsize size = file.tellg();
245 if (size <= compilation_subprocess_shared_memory_size) {
246 file.seekg(0, std::ios::beg);
247 file.read(reinterpret_cast<char *>(shared_mem.get_data()), size);
248 /* Ensure it's valid. */
249 if (!validate_binary(shared_mem.get_data())) {
250 std::cout << "Compilation Subprocess: Failed to load cached shader binary " << hash_str
251 << "\n";
252 /* We can't compile the shader anymore since we have written over the source code,
253 * but we delete the cache for the next time this shader is requested. */
254 file.close();
255 BLI_delete(cache_path.c_str(), false, false);
256 }
257 end_semaphore.increment();
258 continue;
259 }
260 else {
261 /* This should never happen, since shaders larger than the pool size should be discarded
262 * and compiled in the main Blender process. */
263 std::cerr << "Compilation Subprocess: Wrong size for cached shader binary " << hash_str
264 << "\n";
266 }
267 }
268
269 SubprocessShader shader(comp_src, vert_src, geom_src, frag_src);
270 ShaderBinaryHeader *binary = shader.get_binary(shared_mem.get_data());
271
272 end_semaphore.increment();
273
274 if (binary) {
275 fstream file(cache_path, std::ios::binary | std::ios::out);
276 file.write(reinterpret_cast<char *>(shared_mem.get_data()),
277 binary->size + offsetof(ShaderBinaryHeader, data));
278 }
279 }
280
281 GPU_exit();
282 GPU_context_discard(gpu_context);
285}
286
287namespace blender::gpu {
288void GL_shader_cache_dir_clear_old()
289{
290 std::string cache_dir = cache_dir_get();
291
292 direntry *entries = nullptr;
293 uint32_t dir_len = BLI_filelist_dir_contents(cache_dir.c_str(), &entries);
294 for (int i : blender::IndexRange(dir_len)) {
295 direntry entry = entries[i];
296 if (S_ISDIR(entry.s.st_mode)) {
297 continue;
298 }
299 const time_t ts_now = time(nullptr);
300 const time_t delete_threshold = 60 /*seconds*/ * 60 /*minutes*/ * 24 /*hours*/ * 30 /*days*/;
301 if (entry.s.st_mtime + delete_threshold < ts_now) {
302 BLI_delete(entry.path, false, false);
303 }
304 }
305 BLI_filelist_free(entries, dir_len);
306}
307} // namespace blender::gpu
308
309#endif
bool BKE_appdir_folder_caches(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
Definition appdir.cc:226
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
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:350
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:391
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.
Compatibility-like things for windows.
#define S_ISDIR(x)
void CLG_init(void)
Definition clog.c:699
GHOST C-API function and type declarations.
GHOST_ContextHandle GHOST_CreateGPUContext(GHOST_SystemHandle systemhandle, GHOST_GPUSettings gpuSettings)
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()
struct GPUContext GPUContext
FILE * file
double time
EvaluationStage stage
Definition deg_eval.cc:83
#define offsetof(t, d)
static std::optional< std::string > cache_dir_get()
unsigned int uint32_t
Definition stdint.h:80
GHOST_TDrawingContextType context_type
struct stat s
const char * path
#define SEP_STR
Definition unit.cc:39