Skip to content

Commit c0c7556

Browse files
committed
Merge pull request #111129 from DarioSamo/particle-pipeline-deferral
Push pipeline compilation of various effects to the worker thread pool.
2 parents 8ce4f80 + ba26841 commit c0c7556

File tree

15 files changed

+353
-137
lines changed

15 files changed

+353
-137
lines changed

servers/rendering/renderer_rd/effects/bokeh_dof.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ BokehDOF::BokehDOF(bool p_prefer_raster_effects) {
6666

6767
for (int i = 0; i < BOKEH_MAX; i++) {
6868
if (bokeh.compute_shader.is_variant_enabled(i)) {
69-
bokeh.compute_pipelines[i] = RD::get_singleton()->compute_pipeline_create(bokeh.compute_shader.version_get_shader(bokeh.shader_version, i));
69+
bokeh.compute_pipelines[i].create_compute_pipeline(bokeh.compute_shader.version_get_shader(bokeh.shader_version, i));
7070
}
7171
}
7272

@@ -77,6 +77,10 @@ BokehDOF::BokehDOF(bool p_prefer_raster_effects) {
7777
}
7878

7979
BokehDOF::~BokehDOF() {
80+
for (int i = 0; i < BOKEH_MAX; i++) {
81+
bokeh.compute_pipelines[i].free();
82+
}
83+
8084
if (prefer_raster_effects) {
8185
bokeh.raster_shader.version_free(bokeh.shader_version);
8286
} else {
@@ -154,7 +158,7 @@ void BokehDOF::bokeh_dof_compute(const BokehBuffers &p_buffers, RID p_camera_att
154158
RID shader = bokeh.compute_shader.version_get_shader(bokeh.shader_version, BOKEH_GEN_BLUR_SIZE);
155159
ERR_FAIL_COND(shader.is_null());
156160

157-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_GEN_BLUR_SIZE]);
161+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_GEN_BLUR_SIZE].get_rid());
158162

159163
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_base_image), 0);
160164
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_depth_texture), 1);
@@ -173,7 +177,7 @@ void BokehDOF::bokeh_dof_compute(const BokehBuffers &p_buffers, RID p_camera_att
173177
shader = bokeh.compute_shader.version_get_shader(bokeh.shader_version, mode);
174178
ERR_FAIL_COND(shader.is_null());
175179

176-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[mode]);
180+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[mode].get_rid());
177181

178182
static const int quality_samples[4] = { 6, 12, 12, 24 };
179183

@@ -223,7 +227,7 @@ void BokehDOF::bokeh_dof_compute(const BokehBuffers &p_buffers, RID p_camera_att
223227
shader = bokeh.compute_shader.version_get_shader(bokeh.shader_version, BOKEH_COMPOSITE);
224228
ERR_FAIL_COND(shader.is_null());
225229

226-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_COMPOSITE]);
230+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_COMPOSITE].get_rid());
227231

228232
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_base_image), 0);
229233
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_half_texture1), 1);
@@ -244,7 +248,7 @@ void BokehDOF::bokeh_dof_compute(const BokehBuffers &p_buffers, RID p_camera_att
244248
ERR_FAIL_COND(shader.is_null());
245249

246250
//second pass
247-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_GEN_BOKEH_CIRCULAR]);
251+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_GEN_BOKEH_CIRCULAR].get_rid());
248252

249253
static const float quality_scale[4] = { 8.0, 4.0, 1.0, 0.5 };
250254

@@ -272,7 +276,7 @@ void BokehDOF::bokeh_dof_compute(const BokehBuffers &p_buffers, RID p_camera_att
272276
shader = bokeh.compute_shader.version_get_shader(bokeh.shader_version, BOKEH_COMPOSITE);
273277
ERR_FAIL_COND(shader.is_null());
274278

275-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_COMPOSITE]);
279+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, bokeh.compute_pipelines[BOKEH_COMPOSITE].get_rid());
276280

277281
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_base_image), 0);
278282
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_half_texture0), 1);

servers/rendering/renderer_rd/effects/bokeh_dof.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#pragma once
3232

3333
#include "servers/rendering/renderer_rd/pipeline_cache_rd.h"
34+
#include "servers/rendering/renderer_rd/pipeline_deferred_rd.h"
3435
#include "servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl.gen.h"
3536
#include "servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl.gen.h"
3637

@@ -86,7 +87,7 @@ class BokehDOF {
8687
BokehDofShaderRD compute_shader;
8788
BokehDofRasterShaderRD raster_shader;
8889
RID shader_version;
89-
RID compute_pipelines[BOKEH_MAX];
90+
PipelineDeferredRD compute_pipelines[BOKEH_MAX];
9091
PipelineCacheRD raster_pipelines[BOKEH_MAX];
9192
} bokeh;
9293

servers/rendering/renderer_rd/effects/copy_effects.cpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
9696

9797
for (int i = 0; i < COPY_MODE_MAX; i++) {
9898
if (copy.shader.is_variant_enabled(i)) {
99-
copy.pipelines[i] = RD::get_singleton()->compute_pipeline_create(copy.shader.version_get_shader(copy.shader_version, i));
99+
copy.pipelines[i].create_compute_pipeline(copy.shader.version_get_shader(copy.shader_version, i));
100100
}
101101
}
102102
}
@@ -162,7 +162,7 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
162162

163163
cubemap_downsampler.shader_version = cubemap_downsampler.compute_shader.version_create();
164164

165-
cubemap_downsampler.compute_pipeline = RD::get_singleton()->compute_pipeline_create(cubemap_downsampler.compute_shader.version_get_shader(cubemap_downsampler.shader_version, 0));
165+
cubemap_downsampler.compute_pipeline.create_compute_pipeline(cubemap_downsampler.compute_shader.version_get_shader(cubemap_downsampler.shader_version, 0));
166166
cubemap_downsampler.raster_pipeline.clear();
167167
}
168168
}
@@ -216,7 +216,7 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
216216
filter.shader_version = filter.compute_shader.version_create();
217217

218218
for (int i = 0; i < FILTER_MODE_MAX; i++) {
219-
filter.compute_pipelines[i] = RD::get_singleton()->compute_pipeline_create(filter.compute_shader.version_get_shader(filter.shader_version, i));
219+
filter.compute_pipelines[i].create_compute_pipeline(filter.compute_shader.version_get_shader(filter.shader_version, i));
220220
filter.raster_pipelines[i].clear();
221221
}
222222

@@ -249,7 +249,7 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
249249

250250
roughness.shader_version = roughness.compute_shader.version_create();
251251

252-
roughness.compute_pipeline = RD::get_singleton()->compute_pipeline_create(roughness.compute_shader.version_get_shader(roughness.shader_version, 0));
252+
roughness.compute_pipeline.create_compute_pipeline(roughness.compute_shader.version_get_shader(roughness.shader_version, 0));
253253
roughness.raster_pipeline.clear();
254254
}
255255
}
@@ -306,6 +306,17 @@ CopyEffects::CopyEffects(bool p_prefer_raster_effects) {
306306
}
307307

308308
CopyEffects::~CopyEffects() {
309+
for (int i = 0; i < COPY_MODE_MAX; i++) {
310+
copy.pipelines[i].free();
311+
}
312+
313+
for (int i = 0; i < FILTER_MODE_MAX; i++) {
314+
filter.compute_pipelines[i].free();
315+
}
316+
317+
cubemap_downsampler.compute_pipeline.free();
318+
roughness.compute_pipeline.free();
319+
309320
if (prefer_raster_effects) {
310321
blur_raster.shader.version_free(blur_raster.shader_version);
311322
cubemap_downsampler.raster_shader.version_free(cubemap_downsampler.shader_version);
@@ -377,7 +388,7 @@ void CopyEffects::copy_to_rect(RID p_source_rd_texture, RID p_dest_texture, cons
377388
ERR_FAIL_COND(shader.is_null());
378389

379390
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
380-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
391+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
381392
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
382393
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_texture), 3);
383394
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
@@ -412,7 +423,7 @@ void CopyEffects::copy_cubemap_to_panorama(RID p_source_cube, RID p_dest_panoram
412423
ERR_FAIL_COND(shader.is_null());
413424

414425
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
415-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
426+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
416427
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_cube), 0);
417428
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_panorama), 3);
418429
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
@@ -449,7 +460,7 @@ void CopyEffects::copy_depth_to_rect(RID p_source_rd_texture, RID p_dest_texture
449460
ERR_FAIL_COND(shader.is_null());
450461

451462
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
452-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
463+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
453464
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
454465
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_texture), 3);
455466
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
@@ -488,7 +499,7 @@ void CopyEffects::copy_depth_to_rect_and_linearize(RID p_source_rd_texture, RID
488499
ERR_FAIL_COND(shader.is_null());
489500

490501
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
491-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
502+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
492503
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
493504
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_texture), 3);
494505
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
@@ -695,7 +706,7 @@ void CopyEffects::gaussian_blur(RID p_source_rd_texture, RID p_texture, const Re
695706
ERR_FAIL_COND(shader.is_null());
696707

697708
RD::DrawListID compute_list = RD::get_singleton()->compute_list_begin();
698-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
709+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
699710
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
700711
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_texture), 3);
701712

@@ -777,7 +788,7 @@ void CopyEffects::gaussian_glow(RID p_source_rd_texture, RID p_back_texture, con
777788
ERR_FAIL_COND(shader.is_null());
778789

779790
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
780-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[copy_mode]);
791+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[copy_mode].get_rid());
781792
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
782793
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_back_texture), 3);
783794
if (p_auto_exposure.is_valid() && p_first_pass) {
@@ -890,7 +901,7 @@ void CopyEffects::make_mipmap(RID p_source_rd_texture, RID p_dest_texture, const
890901
ERR_FAIL_COND(shader.is_null());
891902

892903
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
893-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
904+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
894905
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
895906
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_texture), 3);
896907
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
@@ -959,7 +970,7 @@ void CopyEffects::set_color(RID p_dest_texture, const Color &p_color, const Rect
959970
ERR_FAIL_COND(shader.is_null());
960971

961972
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
962-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode]);
973+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, copy.pipelines[mode].get_rid());
963974
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 3, u_dest_texture), 3);
964975
RD::get_singleton()->compute_list_set_push_constant(compute_list, &copy.push_constant, sizeof(CopyPushConstant));
965976
RD::get_singleton()->compute_list_dispatch_threads(compute_list, p_region.size.width, p_region.size.height, 1);
@@ -1056,7 +1067,7 @@ void CopyEffects::cubemap_downsample(RID p_source_cubemap, RID p_dest_cubemap, c
10561067
ERR_FAIL_COND(shader.is_null());
10571068

10581069
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
1059-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, cubemap_downsampler.compute_pipeline);
1070+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, cubemap_downsampler.compute_pipeline.get_rid());
10601071
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_cubemap), 0);
10611072
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_dest_cubemap), 1);
10621073

@@ -1133,7 +1144,7 @@ void CopyEffects::cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubema
11331144
ERR_FAIL_COND(shader.is_null());
11341145

11351146
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
1136-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, filter.compute_pipelines[mode]);
1147+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, filter.compute_pipelines[mode].get_rid());
11371148
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_cubemap), 0);
11381149
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, filter.uniform_set, 1);
11391150
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, filter.image_uniform_set, 2);
@@ -1207,7 +1218,7 @@ void CopyEffects::cubemap_roughness(RID p_source_rd_texture, RID p_dest_texture,
12071218
ERR_FAIL_COND(shader.is_null());
12081219

12091220
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
1210-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, roughness.compute_pipeline);
1221+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, roughness.compute_pipeline.get_rid());
12111222

12121223
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_rd_texture), 0);
12131224
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_dest_texture), 1);

servers/rendering/renderer_rd/effects/copy_effects.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#pragma once
3232

3333
#include "servers/rendering/renderer_rd/pipeline_cache_rd.h"
34+
#include "servers/rendering/renderer_rd/pipeline_deferred_rd.h"
3435
#include "servers/rendering/renderer_rd/shaders/effects/blur_raster.glsl.gen.h"
3536
#include "servers/rendering/renderer_rd/shaders/effects/copy.glsl.gen.h"
3637
#include "servers/rendering/renderer_rd/shaders/effects/copy_to_fb.glsl.gen.h"
@@ -161,7 +162,7 @@ class CopyEffects {
161162
CopyPushConstant push_constant;
162163
CopyShaderRD shader;
163164
RID shader_version;
164-
RID pipelines[COPY_MODE_MAX];
165+
PipelineDeferredRD pipelines[COPY_MODE_MAX];
165166

166167
} copy;
167168

@@ -237,7 +238,7 @@ class CopyEffects {
237238
CubemapDownsamplerShaderRD compute_shader;
238239
CubemapDownsamplerRasterShaderRD raster_shader;
239240
RID shader_version;
240-
RID compute_pipeline;
241+
PipelineDeferredRD compute_pipeline;
241242
PipelineCacheRD raster_pipeline;
242243
} cubemap_downsampler;
243244

@@ -259,7 +260,7 @@ class CopyEffects {
259260
CubemapFilterShaderRD compute_shader;
260261
CubemapFilterRasterShaderRD raster_shader;
261262
RID shader_version;
262-
RID compute_pipelines[FILTER_MODE_MAX];
263+
PipelineDeferredRD compute_pipelines[FILTER_MODE_MAX];
263264
PipelineCacheRD raster_pipelines[FILTER_MODE_MAX];
264265

265266
RID uniform_set;
@@ -283,7 +284,7 @@ class CopyEffects {
283284
CubemapRoughnessShaderRD compute_shader;
284285
CubemapRoughnessRasterShaderRD raster_shader;
285286
RID shader_version;
286-
RID compute_pipeline;
287+
PipelineDeferredRD compute_pipeline;
287288
PipelineCacheRD raster_pipeline;
288289
} roughness;
289290

servers/rendering/renderer_rd/effects/fsr.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ FSR::FSR() {
4848
}
4949

5050
shader_version = fsr_shader.version_create();
51-
pipeline = RD::get_singleton()->compute_pipeline_create(fsr_shader.version_get_shader(shader_version, variant));
51+
pipeline.create_compute_pipeline(fsr_shader.version_get_shader(shader_version, variant));
5252
}
5353

5454
FSR::~FSR() {
55+
pipeline.free();
5556
fsr_shader.version_free(shader_version);
5657
}
5758

@@ -82,7 +83,7 @@ void FSR::process(Ref<RenderSceneBuffersRD> p_render_buffers, RID p_source_rd_te
8283
int dispatch_y = (target_size.y + 15) / 16;
8384

8485
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
85-
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipeline);
86+
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipeline.get_rid());
8687

8788
push_constant.resolution_width = internal_size.width;
8889
push_constant.resolution_height = internal_size.height;

servers/rendering/renderer_rd/effects/fsr.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "spatial_upscaler.h"
3434

3535
#include "../storage_rd/render_scene_buffers_rd.h"
36+
#include "servers/rendering/renderer_rd/pipeline_deferred_rd.h"
3637
#include "servers/rendering/renderer_rd/shaders/effects/fsr_upscale.glsl.gen.h"
3738

3839
namespace RendererRD {
@@ -69,7 +70,7 @@ class FSR : public SpatialUpscaler {
6970

7071
FsrUpscaleShaderRD fsr_shader;
7172
RID shader_version;
72-
RID pipeline;
73+
PipelineDeferredRD pipeline;
7374
};
7475

7576
} // namespace RendererRD

0 commit comments

Comments
 (0)