Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/betsy/image_compress_betsy.cpp
20943 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 "rgb_to_rgba.glsl.gen.h"
42
#include "servers/display/display_server.h"
43
44
static Mutex betsy_mutex;
45
static BetsyCompressor *betsy = nullptr;
46
47
static const BetsyShaderType FORMAT_TO_TYPE[BETSY_FORMAT_MAX] = {
48
BETSY_SHADER_BC1_STANDARD,
49
BETSY_SHADER_BC1_DITHER,
50
BETSY_SHADER_BC1_STANDARD,
51
BETSY_SHADER_BC4_SIGNED,
52
BETSY_SHADER_BC4_UNSIGNED,
53
BETSY_SHADER_BC4_SIGNED,
54
BETSY_SHADER_BC4_UNSIGNED,
55
BETSY_SHADER_BC6_SIGNED,
56
BETSY_SHADER_BC6_UNSIGNED,
57
};
58
59
static const RD::DataFormat BETSY_TO_RD_FORMAT[BETSY_FORMAT_MAX] = {
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_R32G32_UINT,
67
RD::DATA_FORMAT_R32G32B32A32_UINT,
68
RD::DATA_FORMAT_R32G32B32A32_UINT,
69
};
70
71
static const Image::Format BETSY_TO_IMAGE_FORMAT[BETSY_FORMAT_MAX] = {
72
Image::FORMAT_DXT1,
73
Image::FORMAT_DXT1,
74
Image::FORMAT_DXT5,
75
Image::FORMAT_RGTC_R,
76
Image::FORMAT_RGTC_R,
77
Image::FORMAT_RGTC_RG,
78
Image::FORMAT_RGTC_RG,
79
Image::FORMAT_BPTC_RGBF,
80
Image::FORMAT_BPTC_RGBFU,
81
};
82
83
void BetsyCompressor::_init() {
84
if (!DisplayServer::can_create_rendering_device()) {
85
return;
86
}
87
88
// Create local RD.
89
RenderingContextDriver *rcd = nullptr;
90
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
91
92
if (rd == nullptr) {
93
#if defined(RD_ENABLED)
94
#if defined(METAL_ENABLED)
95
rcd = memnew(RenderingContextDriverMetal);
96
rd = memnew(RenderingDevice);
97
#endif
98
#if defined(VULKAN_ENABLED)
99
if (rcd == nullptr) {
100
rcd = memnew(RenderingContextDriverVulkan);
101
rd = memnew(RenderingDevice);
102
}
103
#endif
104
#endif
105
if (rcd != nullptr && rd != nullptr) {
106
Error err = rcd->initialize();
107
if (err == OK) {
108
err = rd->initialize(rcd);
109
}
110
111
if (err != OK) {
112
memdelete(rd);
113
memdelete(rcd);
114
rd = nullptr;
115
rcd = nullptr;
116
}
117
}
118
}
119
120
ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice.");
121
122
compress_rd = rd;
123
compress_rcd = rcd;
124
125
// Create the sampler state.
126
RD::SamplerState src_sampler_state;
127
{
128
src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
129
src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
130
src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;
131
src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;
132
src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;
133
}
134
135
src_sampler = compress_rd->sampler_create(src_sampler_state);
136
137
// Initialize RDShaderFiles.
138
{
139
Ref<RDShaderFile> bc1_shader;
140
bc1_shader.instantiate();
141
Error err = bc1_shader->parse_versions_from_text(bc1_shader_glsl);
142
143
if (err != OK) {
144
bc1_shader->print_errors("Betsy BC1 compress shader");
145
}
146
147
// Standard BC1 compression.
148
cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("standard"));
149
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled.is_null());
150
151
cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled);
152
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline.is_null());
153
154
// Dither BC1 variant. Unused, so comment out for now.
155
//cached_shaders[BETSY_SHADER_BC1_DITHER].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("dithered"));
156
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled.is_null());
157
158
//cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled);
159
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline.is_null());
160
}
161
162
{
163
Ref<RDShaderFile> bc4_shader;
164
bc4_shader.instantiate();
165
Error err = bc4_shader->parse_versions_from_text(bc4_shader_glsl);
166
167
if (err != OK) {
168
bc4_shader->print_errors("Betsy BC4 compress shader");
169
}
170
171
// Signed BC4 compression. Unused, so comment out for now.
172
//cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("signed"));
173
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled.is_null());
174
175
//cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled);
176
//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline.is_null());
177
178
// Unsigned BC4 compression.
179
cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("unsigned"));
180
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled.is_null());
181
182
cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled);
183
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline.is_null());
184
}
185
186
{
187
Ref<RDShaderFile> bc6h_shader;
188
bc6h_shader.instantiate();
189
Error err = bc6h_shader->parse_versions_from_text(bc6h_shader_glsl);
190
191
if (err != OK) {
192
bc6h_shader->print_errors("Betsy BC6 compress shader");
193
}
194
195
// Signed BC6 compression.
196
cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("signed"));
197
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled.is_null());
198
199
cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled);
200
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline.is_null());
201
202
// Unsigned BC6 compression.
203
cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("unsigned"));
204
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled.is_null());
205
206
cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled);
207
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline.is_null());
208
}
209
210
{
211
Ref<RDShaderFile> alpha_stitch_shader;
212
alpha_stitch_shader.instantiate();
213
Error err = alpha_stitch_shader->parse_versions_from_text(alpha_stitch_shader_glsl);
214
215
if (err != OK) {
216
alpha_stitch_shader->print_errors("Betsy alpha stitch shader");
217
}
218
cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled = compress_rd->shader_create_from_spirv(alpha_stitch_shader->get_spirv_stages());
219
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled.is_null());
220
221
cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled);
222
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline.is_null());
223
}
224
225
{
226
Ref<RDShaderFile> rgb_to_rgba_shader;
227
rgb_to_rgba_shader.instantiate();
228
Error err = rgb_to_rgba_shader->parse_versions_from_text(rgb_to_rgba_shader_glsl);
229
230
if (err != OK) {
231
rgb_to_rgba_shader->print_errors("Betsy RGB to RGBA shader");
232
}
233
234
// Float32.
235
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_FLOAT].compiled = compress_rd->shader_create_from_spirv(rgb_to_rgba_shader->get_spirv_stages("version_float"));
236
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_FLOAT].compiled.is_null());
237
238
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_FLOAT].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_FLOAT].compiled);
239
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_FLOAT].pipeline.is_null());
240
241
// Float16.
242
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_HALF].compiled = compress_rd->shader_create_from_spirv(rgb_to_rgba_shader->get_spirv_stages("version_half"));
243
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_HALF].compiled.is_null());
244
245
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_HALF].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_HALF].compiled);
246
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_HALF].pipeline.is_null());
247
248
// Unorm8.
249
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM8].compiled = compress_rd->shader_create_from_spirv(rgb_to_rgba_shader->get_spirv_stages("version_unorm8"));
250
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM8].compiled.is_null());
251
252
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM8].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM8].compiled);
253
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM8].pipeline.is_null());
254
255
// Unorm16.
256
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM16].compiled = compress_rd->shader_create_from_spirv(rgb_to_rgba_shader->get_spirv_stages("version_unorm16"));
257
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM16].compiled.is_null());
258
259
cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM16].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM16].compiled);
260
ERR_FAIL_COND(cached_shaders[BETSY_SHADER_RGB_TO_RGBA_UNORM16].pipeline.is_null());
261
}
262
}
263
264
void BetsyCompressor::init() {
265
WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true, "Betsy pump task", true);
266
command_queue.set_pump_task_id(tid);
267
command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid);
268
command_queue.push_and_sync(this, &BetsyCompressor::_init);
269
DEV_ASSERT(task_id == tid);
270
}
271
272
void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
273
task_id = p_pump_task_id;
274
}
275
276
// Yield thread to WTP so other tasks can be done on it.
277
// Automatically regains control as soon a task is pushed to the command queue.
278
void BetsyCompressor::_thread_loop() {
279
while (!exit) {
280
WorkerThreadPool::get_singleton()->yield();
281
command_queue.flush_all();
282
}
283
}
284
285
void BetsyCompressor::_thread_exit() {
286
exit = true;
287
288
if (compress_rd != nullptr) {
289
if (dxt1_encoding_table_buffer.is_valid()) {
290
compress_rd->free_rid(dxt1_encoding_table_buffer);
291
}
292
293
compress_rd->free_rid(src_sampler);
294
295
// Clear the shader cache, pipelines will be unreferenced automatically.
296
for (int i = 0; i < BETSY_SHADER_MAX; i++) {
297
if (cached_shaders[i].compiled.is_valid()) {
298
compress_rd->free_rid(cached_shaders[i].compiled);
299
}
300
}
301
302
// Free the RD (and RCD if necessary).
303
memdelete(compress_rd);
304
compress_rd = nullptr;
305
if (compress_rcd != nullptr) {
306
memdelete(compress_rcd);
307
compress_rcd = nullptr;
308
}
309
}
310
}
311
312
void BetsyCompressor::finish() {
313
command_queue.push(this, &BetsyCompressor::_thread_exit);
314
if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
315
WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
316
task_id = WorkerThreadPool::INVALID_TASK_ID;
317
}
318
}
319
320
// Helper functions.
321
322
static int get_next_multiple(int n, int m) {
323
return n + (m - (n % m));
324
}
325
326
static Error get_src_texture_format(Image *r_img, RD::DataFormat &r_format, bool &r_is_rgb) {
327
r_is_rgb = false;
328
329
switch (r_img->get_format()) {
330
case Image::FORMAT_L8:
331
r_img->convert(Image::FORMAT_RGBA8);
332
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
333
break;
334
335
case Image::FORMAT_LA8:
336
r_img->convert(Image::FORMAT_RGBA8);
337
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
338
break;
339
340
case Image::FORMAT_R8:
341
r_format = RD::DATA_FORMAT_R8_UNORM;
342
break;
343
344
case Image::FORMAT_RG8:
345
r_format = RD::DATA_FORMAT_R8G8_UNORM;
346
break;
347
348
case Image::FORMAT_RGB8:
349
r_is_rgb = true;
350
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
351
break;
352
353
case Image::FORMAT_RGBA8:
354
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
355
break;
356
357
case Image::FORMAT_RH:
358
r_format = RD::DATA_FORMAT_R16_SFLOAT;
359
break;
360
361
case Image::FORMAT_RGH:
362
r_format = RD::DATA_FORMAT_R16G16_SFLOAT;
363
break;
364
365
case Image::FORMAT_RGBH:
366
r_is_rgb = true;
367
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
368
break;
369
370
case Image::FORMAT_RGBAH:
371
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
372
break;
373
374
case Image::FORMAT_RF:
375
r_format = RD::DATA_FORMAT_R32_SFLOAT;
376
break;
377
378
case Image::FORMAT_RGF:
379
r_format = RD::DATA_FORMAT_R32G32_SFLOAT;
380
break;
381
382
case Image::FORMAT_RGBF:
383
r_is_rgb = true;
384
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
385
break;
386
387
case Image::FORMAT_RGBAF:
388
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
389
break;
390
391
case Image::FORMAT_RGBE9995:
392
r_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
393
break;
394
395
case Image::FORMAT_R16:
396
r_format = RD::DATA_FORMAT_R16_UNORM;
397
break;
398
399
case Image::FORMAT_RG16:
400
r_format = RD::DATA_FORMAT_R16G16_UNORM;
401
break;
402
403
case Image::FORMAT_RGB16:
404
r_is_rgb = true;
405
r_format = RD::DATA_FORMAT_R16G16B16A16_UNORM;
406
break;
407
408
case Image::FORMAT_RGBA16:
409
r_format = RD::DATA_FORMAT_R16G16B16A16_UNORM;
410
break;
411
412
default: {
413
return ERR_UNAVAILABLE;
414
}
415
}
416
417
return OK;
418
}
419
420
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
421
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
422
423
// Return an error so that the compression can fall back to cpu compression
424
if (compress_rd == nullptr) {
425
return ERR_CANT_CREATE;
426
}
427
428
if (r_img->is_compressed()) {
429
return ERR_INVALID_DATA;
430
}
431
432
int img_width = r_img->get_width();
433
int img_height = r_img->get_height();
434
if (img_width % 4 != 0 || img_height % 4 != 0) {
435
img_width = img_width <= 2 ? img_width : (img_width + 3) & ~3;
436
img_height = img_height <= 2 ? img_height : (img_height + 3) & ~3;
437
}
438
439
Error err = OK;
440
441
// Destination format.
442
Image::Format dest_format = BETSY_TO_IMAGE_FORMAT[p_format];
443
RD::DataFormat dst_rd_format = BETSY_TO_RD_FORMAT[p_format];
444
445
BetsyShaderType shader_type = FORMAT_TO_TYPE[p_format];
446
BetsyShader shader = cached_shaders[shader_type];
447
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).
448
BetsyShader stitch_shader;
449
bool needs_alpha_block = false;
450
451
switch (p_format) {
452
case BETSY_FORMAT_BC3:
453
case BETSY_FORMAT_BC5_UNSIGNED:
454
needs_alpha_block = true;
455
secondary_shader = cached_shaders[BETSY_SHADER_BC4_UNSIGNED];
456
stitch_shader = cached_shaders[BETSY_SHADER_ALPHA_STITCH];
457
break;
458
default:
459
break;
460
}
461
462
// src_texture format information.
463
RD::TextureFormat src_texture_format;
464
{
465
src_texture_format.array_layers = 1;
466
src_texture_format.depth = 1;
467
src_texture_format.mipmaps = 1;
468
src_texture_format.texture_type = RD::TEXTURE_TYPE_2D;
469
src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
470
}
471
472
bool needs_rgb_to_rgba = false;
473
err = get_src_texture_format(r_img, src_texture_format.format, needs_rgb_to_rgba);
474
475
if (err != OK) {
476
return err;
477
}
478
479
// For the destination format just copy the source format and change the usage bits.
480
RD::TextureFormat dst_texture_format = src_texture_format;
481
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;
482
dst_texture_format.format = dst_rd_format;
483
484
RD::TextureFormat dst_texture_format_alpha;
485
RD::TextureFormat dst_texture_format_combined;
486
487
if (needs_alpha_block) {
488
dst_texture_format_combined = dst_texture_format;
489
dst_texture_format_combined.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
490
491
dst_texture_format.usage_bits |= RD::TEXTURE_USAGE_SAMPLING_BIT;
492
493
dst_texture_format_alpha = dst_texture_format;
494
dst_texture_format_alpha.format = RD::DATA_FORMAT_R32G32_UINT;
495
}
496
497
// Encoding table setup.
498
if ((dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) && dxt1_encoding_table_buffer.is_null()) {
499
LocalVector<float> dxt1_encoding_table;
500
dxt1_encoding_table.resize(256 * 4);
501
502
for (int i = 0; i < 256; i++) {
503
dxt1_encoding_table[i * 2 + 0] = static_cast<float>(stb__OMatch5[i][0]);
504
dxt1_encoding_table[i * 2 + 1] = static_cast<float>(stb__OMatch5[i][1]);
505
dxt1_encoding_table[512 + (i * 2 + 0)] = static_cast<float>(stb__OMatch6[i][0]);
506
dxt1_encoding_table[512 + (i * 2 + 1)] = static_cast<float>(stb__OMatch6[i][1]);
507
}
508
509
dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(dxt1_encoding_table.size() * sizeof(float), Span<float>(dxt1_encoding_table).reinterpret<uint8_t>());
510
}
511
512
const int mip_count = r_img->get_mipmap_count() + 1;
513
514
// Container for the compressed data.
515
Vector<uint8_t> dst_data;
516
dst_data.resize(Image::get_image_data_size(img_width, img_height, dest_format, r_img->has_mipmaps()));
517
uint8_t *dst_data_ptr = dst_data.ptrw();
518
519
Vector<Vector<uint8_t>> src_images;
520
src_images.push_back(Vector<uint8_t>());
521
Vector<uint8_t> *src_image_ptr = src_images.ptrw();
522
523
// Compress each mipmap.
524
for (int i = 0; i < mip_count; i++) {
525
int width, height;
526
Image::get_image_mipmap_offset_and_dimensions(img_width, img_height, dest_format, i, width, height);
527
528
int64_t src_mip_ofs, src_mip_size;
529
int src_mip_w, src_mip_h;
530
r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
531
532
// Set the source texture width and size.
533
src_texture_format.height = height;
534
src_texture_format.width = width;
535
536
// Set the destination texture width and size.
537
dst_texture_format.height = (height + 3) >> 2;
538
dst_texture_format.width = (width + 3) >> 2;
539
540
// Pad textures to nearest block by smearing.
541
if (width != src_mip_w || height != src_mip_h) {
542
const uint8_t *src_mip_read = r_img->ptr() + src_mip_ofs;
543
544
// Reserve the buffer for padded image data.
545
int px_size = Image::get_format_pixel_size(r_img->get_format());
546
src_image_ptr[0].resize(width * height * px_size);
547
uint8_t *ptrw = src_image_ptr[0].ptrw();
548
549
int x = 0, y = 0;
550
for (y = 0; y < src_mip_h; y++) {
551
for (x = 0; x < src_mip_w; x++) {
552
memcpy(ptrw + (width * y + x) * px_size, src_mip_read + (src_mip_w * y + x) * px_size, px_size);
553
}
554
555
// First, smear in x.
556
for (; x < width; x++) {
557
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - 1) * px_size, px_size);
558
}
559
}
560
561
// Then, smear in y.
562
for (; y < height; y++) {
563
for (x = 0; x < width; x++) {
564
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - width) * px_size, px_size);
565
}
566
}
567
} else {
568
// Create a buffer filled with the source mip layer data.
569
src_image_ptr[0].resize(src_mip_size);
570
memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + src_mip_ofs, src_mip_size);
571
}
572
573
// Create the textures on the GPU.
574
RID src_texture;
575
RID dst_texture_primary = compress_rd->texture_create(dst_texture_format, RD::TextureView());
576
577
if (needs_rgb_to_rgba) {
578
// RGB textures cannot be sampled directly on most hardware, so we do a little trick involving a compute shader
579
// which takes the input data as an SSBO and converts it directly into an RGBA image.
580
BetsyShaderType rgb_shader_type = BETSY_SHADER_MAX;
581
582
switch (r_img->get_format()) {
583
case Image::FORMAT_RGB8:
584
rgb_shader_type = BETSY_SHADER_RGB_TO_RGBA_UNORM8;
585
break;
586
case Image::FORMAT_RGBH:
587
rgb_shader_type = BETSY_SHADER_RGB_TO_RGBA_HALF;
588
break;
589
case Image::FORMAT_RGBF:
590
rgb_shader_type = BETSY_SHADER_RGB_TO_RGBA_FLOAT;
591
break;
592
case Image::FORMAT_RGB16:
593
rgb_shader_type = BETSY_SHADER_RGB_TO_RGBA_UNORM16;
594
break;
595
default:
596
break;
597
}
598
599
// The source 'RGB' buffer.
600
RID source_buffer = compress_rd->storage_buffer_create(src_image_ptr[0].size(), src_image_ptr[0].span());
601
602
RD::TextureFormat rgba_texture_format = src_texture_format;
603
rgba_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;
604
src_texture = compress_rd->texture_create(rgba_texture_format, RD::TextureView());
605
606
Vector<RD::Uniform> uniforms;
607
{
608
{
609
RD::Uniform u;
610
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
611
u.binding = 0;
612
u.append_id(source_buffer);
613
uniforms.push_back(u);
614
}
615
{
616
RD::Uniform u;
617
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
618
u.binding = 1;
619
u.append_id(src_texture);
620
uniforms.push_back(u);
621
}
622
}
623
624
BetsyShader &rgb_shader = cached_shaders[rgb_shader_type];
625
626
RID uniform_set = compress_rd->uniform_set_create(uniforms, rgb_shader.compiled, 0);
627
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
628
629
compress_rd->compute_list_bind_compute_pipeline(compute_list, rgb_shader.pipeline);
630
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
631
632
// Prepare the push constant with the mipmap's resolution.
633
RGBToRGBAPushConstant push_constant;
634
push_constant.width = width;
635
push_constant.height = height;
636
637
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(RGBToRGBAPushConstant));
638
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 8) / 8, get_next_multiple(height, 8) / 8, 1);
639
640
compress_rd->compute_list_end();
641
642
compress_rd->free_rid(source_buffer);
643
} else {
644
src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);
645
}
646
647
{
648
Vector<RD::Uniform> uniforms;
649
{
650
{
651
RD::Uniform u;
652
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
653
u.binding = 0;
654
u.append_id(src_sampler);
655
u.append_id(src_texture);
656
uniforms.push_back(u);
657
}
658
{
659
RD::Uniform u;
660
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
661
u.binding = 1;
662
u.append_id(dst_texture_primary);
663
uniforms.push_back(u);
664
}
665
666
if (dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) {
667
RD::Uniform u;
668
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
669
u.binding = 2;
670
u.append_id(dxt1_encoding_table_buffer);
671
uniforms.push_back(u);
672
}
673
}
674
675
RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
676
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
677
678
compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
679
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
680
681
switch (shader_type) {
682
case BETSY_SHADER_BC6_SIGNED:
683
case BETSY_SHADER_BC6_UNSIGNED: {
684
BC6PushConstant push_constant;
685
push_constant.sizeX = 1.0f / width;
686
push_constant.sizeY = 1.0f / height;
687
688
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
689
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
690
} break;
691
692
case BETSY_SHADER_BC1_STANDARD: {
693
BC1PushConstant push_constant;
694
push_constant.num_refines = 2;
695
696
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
697
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
698
} break;
699
700
case BETSY_SHADER_BC4_UNSIGNED: {
701
BC4PushConstant push_constant;
702
push_constant.channel_idx = 0;
703
704
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
705
compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
706
} break;
707
708
default: {
709
} break;
710
}
711
712
compress_rd->compute_list_end();
713
714
if (!needs_alpha_block) {
715
compress_rd->submit();
716
compress_rd->sync();
717
}
718
}
719
720
RID dst_texture_rid = dst_texture_primary;
721
722
if (needs_alpha_block) {
723
// Set the destination texture width and size.
724
dst_texture_format_alpha.height = (height + 3) >> 2;
725
dst_texture_format_alpha.width = (width + 3) >> 2;
726
727
RID dst_texture_alpha = compress_rd->texture_create(dst_texture_format_alpha, RD::TextureView());
728
729
{
730
Vector<RD::Uniform> uniforms;
731
{
732
{
733
RD::Uniform u;
734
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
735
u.binding = 0;
736
u.append_id(src_sampler);
737
u.append_id(src_texture);
738
uniforms.push_back(u);
739
}
740
{
741
RD::Uniform u;
742
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
743
u.binding = 1;
744
u.append_id(dst_texture_alpha);
745
uniforms.push_back(u);
746
}
747
}
748
749
RID uniform_set = compress_rd->uniform_set_create(uniforms, secondary_shader.compiled, 0);
750
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
751
752
compress_rd->compute_list_bind_compute_pipeline(compute_list, secondary_shader.pipeline);
753
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
754
755
BC4PushConstant push_constant;
756
push_constant.channel_idx = dest_format == Image::FORMAT_DXT5 ? 3 : 1;
757
758
compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
759
compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
760
761
compress_rd->compute_list_end();
762
}
763
764
// Stitching
765
766
// Set the destination texture width and size.
767
dst_texture_format_combined.height = (height + 3) >> 2;
768
dst_texture_format_combined.width = (width + 3) >> 2;
769
770
RID dst_texture_combined = compress_rd->texture_create(dst_texture_format_combined, RD::TextureView());
771
772
{
773
Vector<RD::Uniform> uniforms;
774
{
775
{
776
RD::Uniform u;
777
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
778
u.binding = 0;
779
u.append_id(src_sampler);
780
u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_alpha : dst_texture_primary);
781
uniforms.push_back(u);
782
}
783
{
784
RD::Uniform u;
785
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
786
u.binding = 1;
787
u.append_id(src_sampler);
788
u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_primary : dst_texture_alpha);
789
uniforms.push_back(u);
790
}
791
{
792
RD::Uniform u;
793
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
794
u.binding = 2;
795
u.append_id(dst_texture_combined);
796
uniforms.push_back(u);
797
}
798
}
799
800
RID uniform_set = compress_rd->uniform_set_create(uniforms, stitch_shader.compiled, 0);
801
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
802
803
compress_rd->compute_list_bind_compute_pipeline(compute_list, stitch_shader.pipeline);
804
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
805
compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
806
807
compress_rd->compute_list_end();
808
809
compress_rd->submit();
810
compress_rd->sync();
811
}
812
813
dst_texture_rid = dst_texture_combined;
814
815
compress_rd->free_rid(dst_texture_primary);
816
compress_rd->free_rid(dst_texture_alpha);
817
}
818
819
// Copy data from the GPU to the buffer.
820
const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture_rid, 0);
821
int64_t dst_ofs = Image::get_image_mipmap_offset(img_width, img_height, dest_format, i);
822
823
memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());
824
825
// Free the source and dest texture.
826
compress_rd->free_rid(src_texture);
827
compress_rd->free_rid(dst_texture_rid);
828
}
829
830
src_images.clear();
831
832
// Set the compressed data to the image.
833
r_img->set_data(img_width, img_height, r_img->has_mipmaps(), dest_format, dst_data);
834
835
print_verbose(
836
vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",
837
img_width,
838
img_height,
839
r_img->get_mipmap_count(),
840
Image::get_format_name(dest_format),
841
OS::get_singleton()->get_ticks_msec() - start_time));
842
843
return OK;
844
}
845
846
void ensure_betsy_exists() {
847
betsy_mutex.lock();
848
if (betsy == nullptr) {
849
betsy = memnew(BetsyCompressor);
850
betsy->init();
851
}
852
betsy_mutex.unlock();
853
}
854
855
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) {
856
ensure_betsy_exists();
857
Image::Format format = r_img->get_format();
858
Error result = ERR_UNAVAILABLE;
859
860
if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) {
861
if (r_img->detect_signed()) {
862
result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img);
863
} else {
864
result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img);
865
}
866
}
867
868
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
869
free_device();
870
}
871
872
return result;
873
}
874
875
Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
876
ensure_betsy_exists();
877
Error result = ERR_UNAVAILABLE;
878
879
switch (p_channels) {
880
case Image::USED_CHANNELS_RGB:
881
case Image::USED_CHANNELS_L:
882
result = betsy->compress(BETSY_FORMAT_BC1, r_img);
883
break;
884
885
case Image::USED_CHANNELS_RGBA:
886
case Image::USED_CHANNELS_LA:
887
result = betsy->compress(BETSY_FORMAT_BC3, r_img);
888
break;
889
890
case Image::USED_CHANNELS_R:
891
result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);
892
break;
893
894
case Image::USED_CHANNELS_RG:
895
result = betsy->compress(BETSY_FORMAT_BC5_UNSIGNED, r_img);
896
break;
897
898
default:
899
break;
900
}
901
902
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
903
free_device();
904
}
905
906
return result;
907
}
908
909
void free_device() {
910
if (betsy != nullptr) {
911
betsy->finish();
912
memdelete(betsy);
913
}
914
}
915
916