Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/betsy/image_compress_betsy.cpp
10277 views
1
/**************************************************************************/
2
/* image_compress_betsy.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "image_compress_betsy.h"
32
33
#include "core/config/project_settings.h"
34
35
#include "betsy_bc1.h"
36
37
#include "alpha_stitch.glsl.gen.h"
38
#include "bc1.glsl.gen.h"
39
#include "bc4.glsl.gen.h"
40
#include "bc6h.glsl.gen.h"
41
#include "servers/display_server.h"
42
43
static Mutex betsy_mutex;
44
static BetsyCompressor *betsy = nullptr;
45
46
static const BetsyShaderType FORMAT_TO_TYPE[BETSY_FORMAT_MAX] = {
47
BETSY_SHADER_BC1_STANDARD,
48
BETSY_SHADER_BC1_DITHER,
49
BETSY_SHADER_BC1_STANDARD,
50
BETSY_SHADER_BC4_SIGNED,
51
BETSY_SHADER_BC4_UNSIGNED,
52
BETSY_SHADER_BC4_SIGNED,
53
BETSY_SHADER_BC4_UNSIGNED,
54
BETSY_SHADER_BC6_SIGNED,
55
BETSY_SHADER_BC6_UNSIGNED,
56
};
57
58
static const RD::DataFormat BETSY_TO_RD_FORMAT[BETSY_FORMAT_MAX] = {
59
RD::DATA_FORMAT_R32G32_UINT,
60
RD::DATA_FORMAT_R32G32_UINT,
61
RD::DATA_FORMAT_R32G32_UINT,
62
RD::DATA_FORMAT_R32G32_UINT,
63
RD::DATA_FORMAT_R32G32_UINT,
64
RD::DATA_FORMAT_R32G32_UINT,
65
RD::DATA_FORMAT_R32G32_UINT,
66
RD::DATA_FORMAT_R32G32B32A32_UINT,
67
RD::DATA_FORMAT_R32G32B32A32_UINT,
68
};
69
70
static const Image::Format BETSY_TO_IMAGE_FORMAT[BETSY_FORMAT_MAX] = {
71
Image::FORMAT_DXT1,
72
Image::FORMAT_DXT1,
73
Image::FORMAT_DXT5,
74
Image::FORMAT_RGTC_R,
75
Image::FORMAT_RGTC_R,
76
Image::FORMAT_RGTC_RG,
77
Image::FORMAT_RGTC_RG,
78
Image::FORMAT_BPTC_RGBF,
79
Image::FORMAT_BPTC_RGBFU,
80
};
81
82
void BetsyCompressor::_init() {
83
if (!DisplayServer::can_create_rendering_device()) {
84
return;
85
}
86
87
// Create local RD.
88
RenderingContextDriver *rcd = nullptr;
89
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
90
91
if (rd == nullptr) {
92
#if defined(RD_ENABLED)
93
#if defined(METAL_ENABLED)
94
rcd = memnew(RenderingContextDriverMetal);
95
rd = memnew(RenderingDevice);
96
#endif
97
#if defined(VULKAN_ENABLED)
98
if (rcd == nullptr) {
99
rcd = memnew(RenderingContextDriverVulkan);
100
rd = memnew(RenderingDevice);
101
}
102
#endif
103
#endif
104
if (rcd != nullptr && rd != nullptr) {
105
Error err = rcd->initialize();
106
if (err == OK) {
107
err = rd->initialize(rcd);
108
}
109
110
if (err != OK) {
111
memdelete(rd);
112
memdelete(rcd);
113
rd = nullptr;
114
rcd = nullptr;
115
}
116
}
117
}
118
119
ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice.");
120
121
compress_rd = rd;
122
compress_rcd = rcd;
123
124
// Create the sampler state.
125
RD::SamplerState src_sampler_state;
126
{
127
src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
128
src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
129
src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;
130
src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;
131
src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;
132
}
133
134
src_sampler = compress_rd->sampler_create(src_sampler_state);
135
136
// Initialize RDShaderFiles.
137
{
138
Ref<RDShaderFile> bc1_shader;
139
bc1_shader.instantiate();
140
Error err = bc1_shader->parse_versions_from_text(bc1_shader_glsl);
141
142
if (err != OK) {
143
bc1_shader->print_errors("Betsy BC1 compress shader");
144
}
145
146
// Standard BC1 compression.
147
cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("standard"));
148
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled.is_null());
149
150
cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled);
151
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline.is_null());
152
153
// Dither BC1 variant. Unused, so comment out for now.
154
//cached_shaders[BETSY_SHADER_BC1_DITHER].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("dithered"));
155
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled.is_null());
156
157
//cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled);
158
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline.is_null());
159
}
160
161
{
162
Ref<RDShaderFile> bc4_shader;
163
bc4_shader.instantiate();
164
Error err = bc4_shader->parse_versions_from_text(bc4_shader_glsl);
165
166
if (err != OK) {
167
bc4_shader->print_errors("Betsy BC4 compress shader");
168
}
169
170
// Signed BC4 compression. Unused, so comment out for now.
171
//cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("signed"));
172
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled.is_null());
173
174
//cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled);
175
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline.is_null());
176
177
// Unsigned BC4 compression.
178
cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("unsigned"));
179
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled.is_null());
180
181
cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled);
182
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline.is_null());
183
}
184
185
{
186
Ref<RDShaderFile> bc6h_shader;
187
bc6h_shader.instantiate();
188
Error err = bc6h_shader->parse_versions_from_text(bc6h_shader_glsl);
189
190
if (err != OK) {
191
bc6h_shader->print_errors("Betsy BC6 compress shader");
192
}
193
194
// Signed BC6 compression.
195
cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("signed"));
196
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled.is_null());
197
198
cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled);
199
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline.is_null());
200
201
// Unsigned BC6 compression.
202
cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("unsigned"));
203
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled.is_null());
204
205
cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled);
206
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline.is_null());
207
}
208
209
{
210
Ref<RDShaderFile> alpha_stitch_shader;
211
alpha_stitch_shader.instantiate();
212
Error err = alpha_stitch_shader->parse_versions_from_text(alpha_stitch_shader_glsl);
213
214
if (err != OK) {
215
alpha_stitch_shader->print_errors("Betsy alpha stitch shader");
216
}
217
cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled = compress_rd->shader_create_from_spirv(alpha_stitch_shader->get_spirv_stages());
218
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled.is_null());
219
220
cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled);
221
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline.is_null());
222
}
223
}
224
225
void BetsyCompressor::init() {
226
WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true, "Betsy pump task", true);
227
command_queue.set_pump_task_id(tid);
228
command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid);
229
command_queue.push_and_sync(this, &BetsyCompressor::_init);
230
DEV_ASSERT(task_id == tid);
231
}
232
233
void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
234
task_id = p_pump_task_id;
235
}
236
237
// Yield thread to WTP so other tasks can be done on it.
238
// Automatically regains control as soon a task is pushed to the command queue.
239
void BetsyCompressor::_thread_loop() {
240
while (!exit) {
241
WorkerThreadPool::get_singleton()->yield();
242
command_queue.flush_all();
243
}
244
}
245
246
void BetsyCompressor::_thread_exit() {
247
exit = true;
248
249
if (compress_rd != nullptr) {
250
if (dxt1_encoding_table_buffer.is_valid()) {
251
compress_rd->free(dxt1_encoding_table_buffer);
252
}
253
254
compress_rd->free(src_sampler);
255
256
// Clear the shader cache, pipelines will be unreferenced automatically.
257
for (int i = 0; i < BETSY_SHADER_MAX; i++) {
258
if (cached_shaders[i].compiled.is_valid()) {
259
compress_rd->free(cached_shaders[i].compiled);
260
}
261
}
262
263
// Free the RD (and RCD if necessary).
264
memdelete(compress_rd);
265
compress_rd = nullptr;
266
if (compress_rcd != nullptr) {
267
memdelete(compress_rcd);
268
compress_rcd = nullptr;
269
}
270
}
271
}
272
273
void BetsyCompressor::finish() {
274
command_queue.push(this, &BetsyCompressor::_thread_exit);
275
if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
276
WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
277
task_id = WorkerThreadPool::INVALID_TASK_ID;
278
}
279
}
280
281
// Helper functions.
282
283
static int get_next_multiple(int n, int m) {
284
return n + (m - (n % m));
285
}
286
287
static Error get_src_texture_format(Image *r_img, RD::DataFormat &r_format) {
288
switch (r_img->get_format()) {
289
case Image::FORMAT_L8:
290
r_img->convert(Image::FORMAT_RGBA8);
291
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
292
break;
293
294
case Image::FORMAT_LA8:
295
r_img->convert(Image::FORMAT_RGBA8);
296
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
297
break;
298
299
case Image::FORMAT_R8:
300
r_format = RD::DATA_FORMAT_R8_UNORM;
301
break;
302
303
case Image::FORMAT_RG8:
304
r_format = RD::DATA_FORMAT_R8G8_UNORM;
305
break;
306
307
case Image::FORMAT_RGB8:
308
r_img->convert(Image::FORMAT_RGBA8);
309
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
310
break;
311
312
case Image::FORMAT_RGBA8:
313
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
314
break;
315
316
case Image::FORMAT_RH:
317
r_format = RD::DATA_FORMAT_R16_SFLOAT;
318
break;
319
320
case Image::FORMAT_RGH:
321
r_format = RD::DATA_FORMAT_R16G16_SFLOAT;
322
break;
323
324
case Image::FORMAT_RGBH:
325
r_img->convert(Image::FORMAT_RGBAH);
326
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
327
break;
328
329
case Image::FORMAT_RGBAH:
330
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
331
break;
332
333
case Image::FORMAT_RF:
334
r_format = RD::DATA_FORMAT_R32_SFLOAT;
335
break;
336
337
case Image::FORMAT_RGF:
338
r_format = RD::DATA_FORMAT_R32G32_SFLOAT;
339
break;
340
341
case Image::FORMAT_RGBF:
342
r_img->convert(Image::FORMAT_RGBAF);
343
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
344
break;
345
346
case Image::FORMAT_RGBAF:
347
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
348
break;
349
350
case Image::FORMAT_RGBE9995:
351
r_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
352
break;
353
354
default: {
355
return ERR_UNAVAILABLE;
356
}
357
}
358
359
return OK;
360
}
361
362
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
363
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
364
365
// Return an error so that the compression can fall back to cpu compression
366
if (compress_rd == nullptr) {
367
return ERR_CANT_CREATE;
368
}
369
370
if (r_img->is_compressed()) {
371
return ERR_INVALID_DATA;
372
}
373
374
int img_width = r_img->get_width();
375
int img_height = r_img->get_height();
376
if (img_width % 4 != 0 || img_height % 4 != 0) {
377
img_width = img_width <= 2 ? img_width : (img_width + 3) & ~3;
378
img_height = img_height <= 2 ? img_height : (img_height + 3) & ~3;
379
}
380
381
Error err = OK;
382
383
// Destination format.
384
Image::Format dest_format = BETSY_TO_IMAGE_FORMAT[p_format];
385
RD::DataFormat dst_rd_format = BETSY_TO_RD_FORMAT[p_format];
386
387
BetsyShaderType shader_type = FORMAT_TO_TYPE[p_format];
388
BetsyShader shader = cached_shaders[shader_type];
389
BetsyShader secondary_shader; // The secondary shader is used for alpha blocks. For BC it's BC4U and for ETC it's ETC2_RU (8-bit variant).
390
BetsyShader stitch_shader;
391
bool needs_alpha_block = false;
392
393
switch (p_format) {
394
case BETSY_FORMAT_BC3:
395
case BETSY_FORMAT_BC5_UNSIGNED:
396
needs_alpha_block = true;
397
secondary_shader = cached_shaders[BETSY_SHADER_BC4_UNSIGNED];
398
stitch_shader = cached_shaders[BETSY_SHADER_ALPHA_STITCH];
399
break;
400
default:
401
break;
402
}
403
404
// src_texture format information.
405
RD::TextureFormat src_texture_format;
406
{
407
src_texture_format.array_layers = 1;
408
src_texture_format.depth = 1;
409
src_texture_format.mipmaps = 1;
410
src_texture_format.texture_type = RD::TEXTURE_TYPE_2D;
411
src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
412
}
413
414
err = get_src_texture_format(r_img, src_texture_format.format);
415
416
if (err != OK) {
417
return err;
418
}
419
420
// For the destination format just copy the source format and change the usage bits.
421
RD::TextureFormat dst_texture_format = src_texture_format;
422
dst_texture_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
423
dst_texture_format.format = dst_rd_format;
424
425
RD::TextureFormat dst_texture_format_alpha;
426
RD::TextureFormat dst_texture_format_combined;
427
428
if (needs_alpha_block) {
429
dst_texture_format_combined = dst_texture_format;
430
dst_texture_format_combined.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
431
432
dst_texture_format.usage_bits |= RD::TEXTURE_USAGE_SAMPLING_BIT;
433
434
dst_texture_format_alpha = dst_texture_format;
435
dst_texture_format_alpha.format = RD::DATA_FORMAT_R32G32_UINT;
436
}
437
438
// Encoding table setup.
439
if ((dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) && dxt1_encoding_table_buffer.is_null()) {
440
dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, Span(dxt1_encoding_table).reinterpret<uint8_t>());
441
}
442
443
const int mip_count = r_img->get_mipmap_count() + 1;
444
445
// Container for the compressed data.
446
Vector<uint8_t> dst_data;
447
dst_data.resize(Image::get_image_data_size(img_width, img_height, dest_format, r_img->has_mipmaps()));
448
uint8_t *dst_data_ptr = dst_data.ptrw();
449
450
Vector<Vector<uint8_t>> src_images;
451
src_images.push_back(Vector<uint8_t>());
452
Vector<uint8_t> *src_image_ptr = src_images.ptrw();
453
454
// Compress each mipmap.
455
for (int i = 0; i < mip_count; i++) {
456
int width, height;
457
Image::get_image_mipmap_offset_and_dimensions(img_width, img_height, dest_format, i, width, height);
458
459
int64_t src_mip_ofs, src_mip_size;
460
int src_mip_w, src_mip_h;
461
r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
462
463
// Set the source texture width and size.
464
src_texture_format.height = height;
465
src_texture_format.width = width;
466
467
// Set the destination texture width and size.
468
dst_texture_format.height = (height + 3) >> 2;
469
dst_texture_format.width = (width + 3) >> 2;
470
471
// Pad textures to nearest block by smearing.
472
if (width != src_mip_w || height != src_mip_h) {
473
const uint8_t *src_mip_read = r_img->ptr() + src_mip_ofs;
474
475
// Reserve the buffer for padded image data.
476
int px_size = Image::get_format_pixel_size(r_img->get_format());
477
src_image_ptr[0].resize(width * height * px_size);
478
uint8_t *ptrw = src_image_ptr[0].ptrw();
479
480
int x = 0, y = 0;
481
for (y = 0; y < src_mip_h; y++) {
482
for (x = 0; x < src_mip_w; x++) {
483
memcpy(ptrw + (width * y + x) * px_size, src_mip_read + (src_mip_w * y + x) * px_size, px_size);
484
}
485
486
// First, smear in x.
487
for (; x < width; x++) {
488
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - 1) * px_size, px_size);
489
}
490
}
491
492
// Then, smear in y.
493
for (; y < height; y++) {
494
for (x = 0; x < width; x++) {
495
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - width) * px_size, px_size);
496
}
497
}
498
} else {
499
// Create a buffer filled with the source mip layer data.
500
src_image_ptr[0].resize(src_mip_size);
501
memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + src_mip_ofs, src_mip_size);
502
}
503
504
// Create the textures on the GPU.
505
RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);
506
RID dst_texture_primary = compress_rd->texture_create(dst_texture_format, RD::TextureView());
507
508
{
509
Vector<RD::Uniform> uniforms;
510
{
511
{
512
RD::Uniform u;
513
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
514
u.binding = 0;
515
u.append_id(src_sampler);
516
u.append_id(src_texture);
517
uniforms.push_back(u);
518
}
519
{
520
RD::Uniform u;
521
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
522
u.binding = 1;
523
u.append_id(dst_texture_primary);
524
uniforms.push_back(u);
525
}
526
527
if (dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) {
528
RD::Uniform u;
529
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
530
u.binding = 2;
531
u.append_id(dxt1_encoding_table_buffer);
532
uniforms.push_back(u);
533
}
534
}
535
536
RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
537
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
538
539
compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
540
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
541
542
switch (shader_type) {
543
case BETSY_SHADER_BC6_SIGNED:
544
case BETSY_SHADER_BC6_UNSIGNED: {
545
BC6PushConstant push_constant;
546
push_constant.sizeX = 1.0f / width;
547
push_constant.sizeY = 1.0f / height;
548
549
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
550
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
551
} break;
552
553
case BETSY_SHADER_BC1_STANDARD: {
554
BC1PushConstant push_constant;
555
push_constant.num_refines = 2;
556
557
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
558
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
559
} break;
560
561
case BETSY_SHADER_BC4_UNSIGNED: {
562
BC4PushConstant push_constant;
563
push_constant.channel_idx = 0;
564
565
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
566
compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
567
} break;
568
569
default: {
570
} break;
571
}
572
573
compress_rd->compute_list_end();
574
575
if (!needs_alpha_block) {
576
compress_rd->submit();
577
compress_rd->sync();
578
}
579
}
580
581
RID dst_texture_rid = dst_texture_primary;
582
583
if (needs_alpha_block) {
584
// Set the destination texture width and size.
585
dst_texture_format_alpha.height = (height + 3) >> 2;
586
dst_texture_format_alpha.width = (width + 3) >> 2;
587
588
RID dst_texture_alpha = compress_rd->texture_create(dst_texture_format_alpha, RD::TextureView());
589
590
{
591
Vector<RD::Uniform> uniforms;
592
{
593
{
594
RD::Uniform u;
595
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
596
u.binding = 0;
597
u.append_id(src_sampler);
598
u.append_id(src_texture);
599
uniforms.push_back(u);
600
}
601
{
602
RD::Uniform u;
603
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
604
u.binding = 1;
605
u.append_id(dst_texture_alpha);
606
uniforms.push_back(u);
607
}
608
}
609
610
RID uniform_set = compress_rd->uniform_set_create(uniforms, secondary_shader.compiled, 0);
611
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
612
613
compress_rd->compute_list_bind_compute_pipeline(compute_list, secondary_shader.pipeline);
614
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
615
616
BC4PushConstant push_constant;
617
push_constant.channel_idx = dest_format == Image::FORMAT_DXT5 ? 3 : 1;
618
619
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
620
compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
621
622
compress_rd->compute_list_end();
623
}
624
625
// Stitching
626
627
// Set the destination texture width and size.
628
dst_texture_format_combined.height = (height + 3) >> 2;
629
dst_texture_format_combined.width = (width + 3) >> 2;
630
631
RID dst_texture_combined = compress_rd->texture_create(dst_texture_format_combined, RD::TextureView());
632
633
{
634
Vector<RD::Uniform> uniforms;
635
{
636
{
637
RD::Uniform u;
638
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
639
u.binding = 0;
640
u.append_id(src_sampler);
641
u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_alpha : dst_texture_primary);
642
uniforms.push_back(u);
643
}
644
{
645
RD::Uniform u;
646
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
647
u.binding = 1;
648
u.append_id(src_sampler);
649
u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_primary : dst_texture_alpha);
650
uniforms.push_back(u);
651
}
652
{
653
RD::Uniform u;
654
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
655
u.binding = 2;
656
u.append_id(dst_texture_combined);
657
uniforms.push_back(u);
658
}
659
}
660
661
RID uniform_set = compress_rd->uniform_set_create(uniforms, stitch_shader.compiled, 0);
662
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
663
664
compress_rd->compute_list_bind_compute_pipeline(compute_list, stitch_shader.pipeline);
665
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
666
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
667
668
compress_rd->compute_list_end();
669
670
compress_rd->submit();
671
compress_rd->sync();
672
}
673
674
dst_texture_rid = dst_texture_combined;
675
676
compress_rd->free(dst_texture_primary);
677
compress_rd->free(dst_texture_alpha);
678
}
679
680
// Copy data from the GPU to the buffer.
681
const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture_rid, 0);
682
int64_t dst_ofs = Image::get_image_mipmap_offset(img_width, img_height, dest_format, i);
683
684
memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());
685
686
// Free the source and dest texture.
687
compress_rd->free(src_texture);
688
compress_rd->free(dst_texture_rid);
689
}
690
691
src_images.clear();
692
693
// Set the compressed data to the image.
694
r_img->set_data(img_width, img_height, r_img->has_mipmaps(), dest_format, dst_data);
695
696
print_verbose(
697
vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",
698
img_width,
699
img_height,
700
r_img->get_mipmap_count(),
701
Image::get_format_name(dest_format),
702
OS::get_singleton()->get_ticks_msec() - start_time));
703
704
return OK;
705
}
706
707
void ensure_betsy_exists() {
708
betsy_mutex.lock();
709
if (betsy == nullptr) {
710
betsy = memnew(BetsyCompressor);
711
betsy->init();
712
}
713
betsy_mutex.unlock();
714
}
715
716
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) {
717
ensure_betsy_exists();
718
Image::Format format = r_img->get_format();
719
Error result = ERR_UNAVAILABLE;
720
721
if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) {
722
if (r_img->detect_signed()) {
723
result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img);
724
} else {
725
result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img);
726
}
727
}
728
729
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
730
free_device();
731
}
732
733
return result;
734
}
735
736
Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
737
ensure_betsy_exists();
738
Error result = ERR_UNAVAILABLE;
739
740
switch (p_channels) {
741
case Image::USED_CHANNELS_RGB:
742
case Image::USED_CHANNELS_L:
743
result = betsy->compress(BETSY_FORMAT_BC1, r_img);
744
break;
745
746
case Image::USED_CHANNELS_RGBA:
747
case Image::USED_CHANNELS_LA:
748
result = betsy->compress(BETSY_FORMAT_BC3, r_img);
749
break;
750
751
case Image::USED_CHANNELS_R:
752
result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);
753
break;
754
755
case Image::USED_CHANNELS_RG:
756
result = betsy->compress(BETSY_FORMAT_BC5_UNSIGNED, r_img);
757
break;
758
759
default:
760
break;
761
}
762
763
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
764
free_device();
765
}
766
767
return result;
768
}
769
770
void free_device() {
771
if (betsy != nullptr) {
772
betsy->finish();
773
memdelete(betsy);
774
}
775
}
776
777