Blender V5.0
image_drawing_mode.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
5#include "draw_view_data.hh"
6
8#include "image_instance.hh"
9#include "image_shader.hh"
10
11#include "BKE_image.hh"
13
14namespace blender::image_engine {
15
16void ScreenSpaceDrawingMode::add_shgroups() const
17{
18 PassSimple &pass = instance_.state.image_ps;
19 gpu::Shader *shader = ShaderModule::module_get().color.get();
20 const ShaderParameters &sh_params = instance_.state.sh_params;
21 DefaultTextureList *dtxl = DRW_context_get()->viewport_texture_list_get();
22
23 pass.shader_set(shader);
24 pass.push_constant("far_near_distances", sh_params.far_near);
25 pass.push_constant("shuffle", sh_params.shuffle);
26 pass.push_constant("draw_flags", int32_t(sh_params.flags));
27 pass.push_constant("is_image_premultiplied", sh_params.use_premul_alpha);
28 pass.bind_texture("depth_tx", dtxl->depth);
29
30 float4x4 image_mat = float4x4::identity();
31 ResourceHandleRange handle = instance_.manager->resource_handle(image_mat);
32 for (const TextureInfo &info : instance_.state.texture_infos) {
33 PassSimple::Sub &sub = pass.sub("Texture");
34 sub.push_constant("offset", info.offset());
35 sub.bind_texture("image_tx", info.texture);
36 sub.draw(info.batch, handle);
37 }
38}
39
40void ScreenSpaceDrawingMode::add_depth_shgroups(::Image *image, ImageUser *image_user) const
41{
42 PassSimple &pass = instance_.state.depth_ps;
43 gpu::Shader *shader = ShaderModule::module_get().depth.get();
44 pass.shader_set(shader);
45
46 float4x4 image_mat = float4x4::identity();
47 ResourceHandleRange handle = instance_.manager->resource_handle(image_mat);
48
49 ImageUser tile_user = {nullptr};
50 if (image_user) {
51 tile_user = *image_user;
52 }
53
54 for (const TextureInfo &info : instance_.state.texture_infos) {
55 LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
56 const ImageTileWrapper image_tile(image_tile_ptr);
57 const int tile_x = image_tile.get_tile_x_offset();
58 const int tile_y = image_tile.get_tile_y_offset();
59 tile_user.tile = image_tile.get_tile_number();
60
61 /* NOTE: `BKE_image_has_ibuf` doesn't work as it fails for render results. That could be a
62 * bug or a feature. For now we just acquire to determine if there is a texture. */
63 void *lock;
64 ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, &lock);
65 if (tile_buffer != nullptr) {
66 instance_.state.float_buffers.mark_used(tile_buffer);
67 PassSimple::Sub &sub = pass.sub("Tile");
68 float4 min_max_uv(tile_x, tile_y, tile_x + 1, tile_y + 1);
69 sub.push_constant("min_max_uv", min_max_uv);
70 sub.draw(info.batch, handle);
71 }
72 BKE_image_release_ibuf(image, tile_buffer, lock);
73 }
74 }
75}
76
77void ScreenSpaceDrawingMode::update_textures(::Image *image, ImageUser *image_user) const
78{
79 State &state = instance_.state;
80 PartialUpdateChecker<ImageTileData> checker(image, image_user, state.partial_update.user);
81 PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes();
82
83 switch (changes.get_result_code()) {
84 case ePartialUpdateCollectResult::FullUpdateNeeded:
85 state.mark_all_texture_slots_dirty();
86 state.float_buffers.clear();
87 break;
88 case ePartialUpdateCollectResult::NoChangesDetected:
89 break;
90 case ePartialUpdateCollectResult::PartialChangesDetected:
91 /* Partial update when wrap repeat is enabled is not supported. */
92 if (state.flags.do_tile_drawing) {
93 state.float_buffers.clear();
94 state.mark_all_texture_slots_dirty();
95 }
96 else {
97 do_partial_update(changes);
98 }
99 break;
100 }
101 do_full_update_for_dirty_textures(image_user);
102}
103
104void ScreenSpaceDrawingMode::do_partial_update_float_buffer(
105 ImBuf *float_buffer, PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const
106{
107 ImBuf *src = iterator.tile_data.tile_buffer;
108 BLI_assert(float_buffer->float_buffer.data != nullptr);
109 BLI_assert(float_buffer->byte_buffer.data == nullptr);
110 BLI_assert(src->float_buffer.data == nullptr);
111 BLI_assert(src->byte_buffer.data != nullptr);
112
113 /* Calculate the overlap between the updated region and the buffer size. Partial Update Checker
114 * always returns a tile (256x256). Which could lay partially outside the buffer when using
115 * different resolutions.
116 */
117 rcti buffer_rect;
118 BLI_rcti_init(&buffer_rect, 0, float_buffer->x, 0, float_buffer->y);
119 rcti clipped_update_region;
120 const bool has_overlap = BLI_rcti_isect(
121 &buffer_rect, &iterator.changed_region.region, &clipped_update_region);
122 if (!has_overlap) {
123 return;
124 }
125
126 IMB_float_from_byte_ex(float_buffer, src, &clipped_update_region);
127}
128
129void ScreenSpaceDrawingMode::do_partial_update(
131{
132 while (iterator.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
133 /* Quick exit when tile_buffer isn't available. */
134 if (iterator.tile_data.tile_buffer == nullptr) {
135 continue;
136 }
137 ImBuf *tile_buffer = instance_.state.float_buffers.cached_float_buffer(
138 iterator.tile_data.tile_buffer);
139 if (tile_buffer != iterator.tile_data.tile_buffer) {
140 do_partial_update_float_buffer(tile_buffer, iterator);
141 }
142
143 const float tile_width = float(iterator.tile_data.tile_buffer->x);
144 const float tile_height = float(iterator.tile_data.tile_buffer->y);
145
146 for (const TextureInfo &info : instance_.state.texture_infos) {
147 /* Dirty images will receive a full update. No need to do a partial one now. */
148 if (info.need_full_update) {
149 continue;
150 }
151 gpu::Texture *texture = info.texture;
152 const float texture_width = GPU_texture_width(texture);
153 const float texture_height = GPU_texture_height(texture);
154 /* TODO: early bound check. */
155 ImageTileWrapper tile_accessor(iterator.tile_data.tile);
156 float tile_offset_x = float(tile_accessor.get_tile_x_offset());
157 float tile_offset_y = float(tile_accessor.get_tile_y_offset());
158 rcti *changed_region_in_texel_space = &iterator.changed_region.region;
159 rctf changed_region_in_uv_space;
161 &changed_region_in_uv_space,
162 float(changed_region_in_texel_space->xmin) / float(iterator.tile_data.tile_buffer->x) +
163 tile_offset_x,
164 float(changed_region_in_texel_space->xmax) / float(iterator.tile_data.tile_buffer->x) +
165 tile_offset_x,
166 float(changed_region_in_texel_space->ymin) / float(iterator.tile_data.tile_buffer->y) +
167 tile_offset_y,
168 float(changed_region_in_texel_space->ymax) / float(iterator.tile_data.tile_buffer->y) +
169 tile_offset_y);
170 rctf changed_overlapping_region_in_uv_space;
171 const bool region_overlap = BLI_rctf_isect(&info.clipping_uv_bounds,
172 &changed_region_in_uv_space,
173 &changed_overlapping_region_in_uv_space);
174 if (!region_overlap) {
175 continue;
176 }
177 /* Convert the overlapping region to texel space and to ss_pixel space...
178 * TODO: first convert to ss_pixel space as integer based. and from there go back to texel
179 * space. But perhaps this isn't needed and we could use an extraction offset somehow. */
180 rcti gpu_texture_region_to_update;
182 &gpu_texture_region_to_update,
183 floor((changed_overlapping_region_in_uv_space.xmin - info.clipping_uv_bounds.xmin) *
184 texture_width / BLI_rctf_size_x(&info.clipping_uv_bounds)),
185 floor((changed_overlapping_region_in_uv_space.xmax - info.clipping_uv_bounds.xmin) *
186 texture_width / BLI_rctf_size_x(&info.clipping_uv_bounds)),
187 ceil((changed_overlapping_region_in_uv_space.ymin - info.clipping_uv_bounds.ymin) *
188 texture_height / BLI_rctf_size_y(&info.clipping_uv_bounds)),
189 ceil((changed_overlapping_region_in_uv_space.ymax - info.clipping_uv_bounds.ymin) *
190 texture_height / BLI_rctf_size_y(&info.clipping_uv_bounds)));
191 gpu_texture_region_to_update.xmax = min_ii(gpu_texture_region_to_update.xmax,
192 info.clipping_bounds.xmax);
193 gpu_texture_region_to_update.ymax = min_ii(gpu_texture_region_to_update.ymax,
194 info.clipping_bounds.ymax);
195
196 rcti tile_region_to_extract;
198 &tile_region_to_extract,
199 floor((changed_overlapping_region_in_uv_space.xmin - tile_offset_x) * tile_width),
200 floor((changed_overlapping_region_in_uv_space.xmax - tile_offset_x) * tile_width),
201 ceil((changed_overlapping_region_in_uv_space.ymin - tile_offset_y) * tile_height),
202 ceil((changed_overlapping_region_in_uv_space.ymax - tile_offset_y) * tile_height));
203
204 /* Create an image buffer with a size.
205 * Extract and scale into an imbuf. */
206 const int texture_region_width = BLI_rcti_size_x(&gpu_texture_region_to_update);
207 const int texture_region_height = BLI_rcti_size_y(&gpu_texture_region_to_update);
208
209 ImBuf extracted_buffer;
211 &extracted_buffer, texture_region_width, texture_region_height, 32, IB_float_data);
212
213 int offset = 0;
214 for (int y = gpu_texture_region_to_update.ymin; y < gpu_texture_region_to_update.ymax; y++) {
215 float yf = y / float(texture_height);
216 float v = info.clipping_uv_bounds.ymax * yf + info.clipping_uv_bounds.ymin * (1.0 - yf) -
217 tile_offset_y;
218 for (int x = gpu_texture_region_to_update.xmin; x < gpu_texture_region_to_update.xmax; x++)
219 {
220 float xf = x / float(texture_width);
221 float u = info.clipping_uv_bounds.xmax * xf + info.clipping_uv_bounds.xmin * (1.0 - xf) -
222 tile_offset_x;
224 &extracted_buffer.float_buffer.data[offset * 4],
225 u * tile_buffer->x,
226 v * tile_buffer->y);
227 offset++;
228 }
229 }
230 IMB_gpu_clamp_half_float(&extracted_buffer);
231
234 extracted_buffer.float_buffer.data,
235 gpu_texture_region_to_update.xmin,
236 gpu_texture_region_to_update.ymin,
237 0,
238 extracted_buffer.x,
239 extracted_buffer.y,
240 0);
241 IMB_free_all_data(&extracted_buffer);
242 }
243 }
244}
245
246void ScreenSpaceDrawingMode::do_full_update_for_dirty_textures(const ImageUser *image_user) const
247{
248 for (TextureInfo &info : instance_.state.texture_infos) {
249 if (!info.need_full_update) {
250 continue;
251 }
252 do_full_update_gpu_texture(info, image_user);
253 }
254}
255
256void ScreenSpaceDrawingMode::do_full_update_gpu_texture(TextureInfo &info,
257 const ImageUser *image_user) const
258{
259 ImBuf texture_buffer;
260 const int texture_width = GPU_texture_width(info.texture);
261 const int texture_height = GPU_texture_height(info.texture);
262 IMB_initImBuf(&texture_buffer, texture_width, texture_height, 0, IB_float_data);
263 ImageUser tile_user = {nullptr};
264 if (image_user) {
265 tile_user = *image_user;
266 }
267
268 void *lock;
269
270 ::Image *image = instance_.state.image;
271 LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
272 const ImageTileWrapper image_tile(image_tile_ptr);
273 tile_user.tile = image_tile.get_tile_number();
274
275 ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, &lock);
276 if (tile_buffer != nullptr) {
277 do_full_update_texture_slot(info, texture_buffer, *tile_buffer, image_tile);
278 }
279 BKE_image_release_ibuf(image, tile_buffer, lock);
280 }
281 IMB_gpu_clamp_half_float(&texture_buffer);
282 GPU_texture_update(info.texture, GPU_DATA_FLOAT, texture_buffer.float_buffer.data);
283 IMB_free_all_data(&texture_buffer);
284}
285
286void ScreenSpaceDrawingMode::do_full_update_texture_slot(const TextureInfo &texture_info,
287 ImBuf &texture_buffer,
288 ImBuf &tile_buffer,
289 const ImageTileWrapper &image_tile) const
290{
291 const int texture_width = texture_buffer.x;
292 const int texture_height = texture_buffer.y;
293 ImBuf *float_tile_buffer = instance_.state.float_buffers.cached_float_buffer(&tile_buffer);
294
295 /* IMB_transform works in a non-consistent space. This should be documented or fixed!.
296 * Construct a variant of the info_uv_to_texture that adds the texel space
297 * transformation. */
298 float3x3 uv_to_texel;
299 rctf texture_area;
300 rctf tile_area;
301
302 BLI_rctf_init(&texture_area, 0.0, texture_width, 0.0, texture_height);
304 &tile_area,
305 tile_buffer.x * (texture_info.clipping_uv_bounds.xmin - image_tile.get_tile_x_offset()),
306 tile_buffer.x * (texture_info.clipping_uv_bounds.xmax - image_tile.get_tile_x_offset()),
307 tile_buffer.y * (texture_info.clipping_uv_bounds.ymin - image_tile.get_tile_y_offset()),
308 tile_buffer.y * (texture_info.clipping_uv_bounds.ymax - image_tile.get_tile_y_offset()));
309 BLI_rctf_transform_calc_m3_pivot_min(&tile_area, &texture_area, uv_to_texel.ptr());
310 uv_to_texel = math::invert(uv_to_texel);
311
312 rctf crop_rect;
313 const rctf *crop_rect_ptr = nullptr;
314 eIMBTransformMode transform_mode;
315 if (instance_.state.flags.do_tile_drawing) {
316 transform_mode = IMB_TRANSFORM_MODE_WRAP_REPEAT;
317 }
318 else {
319 BLI_rctf_init(&crop_rect, 0.0, tile_buffer.x, 0.0, tile_buffer.y);
320 crop_rect_ptr = &crop_rect;
321 transform_mode = IMB_TRANSFORM_MODE_CROP_SRC;
322 }
323
324 IMB_transform(float_tile_buffer,
325 &texture_buffer,
326 transform_mode,
328 uv_to_texel,
329 crop_rect_ptr);
330}
331
333{
334 {
336 instance_.state.depth_fb.ensure(GPU_ATTACHMENT_TEXTURE(dtxl->depth));
337 instance_.state.color_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(dtxl->color));
338 }
339 {
340 PassSimple &pass = instance_.state.image_ps;
341 pass.init();
343 }
344 {
345 PassSimple &pass = instance_.state.depth_ps;
346 pass.init();
348 }
349}
350
352{
353 State &state = instance_.state;
354
355 state.partial_update.ensure_image(image);
356 state.clear_need_full_update_flag();
357 state.float_buffers.reset_usage_flags();
358
359 /* Step: Find out which screen space textures are needed to draw on the screen. Recycle
360 * textures that are not on screen anymore. */
361 OneTexture method(&state);
362 method.ensure_texture_infos();
363 method.update_bounds(instance_.region);
364
365 /* Step: Check for changes in the image user compared to the last time. */
366 state.update_image_usage(iuser);
367
368 /* Step: Update the GPU textures based on the changes in the image. */
370 update_textures(image, iuser);
371
372 /* Step: Add the GPU textures to the shgroup. */
373 state.update_batches();
374 if (!state.flags.do_tile_drawing) {
375 add_depth_shgroups(image, iuser);
376 }
377 add_shgroups();
378}
379
381{
382 instance_.state.float_buffers.remove_unused_buffers();
383}
384
386{
387 float clear_depth = instance_.state.flags.do_tile_drawing ? 0.75 : 1.0f;
388 GPU_framebuffer_bind(instance_.state.depth_fb);
389 instance_.state.depth_fb.clear_depth(clear_depth);
390 instance_.manager->submit(instance_.state.depth_ps, instance_.state.view);
391
392 GPU_framebuffer_bind(instance_.state.color_fb);
393 float4 clear_color = float4(0.0);
394 GPU_framebuffer_clear_color(instance_.state.color_fb, clear_color);
395 instance_.manager->submit(instance_.state.image_ps, instance_.state.view);
396}
397
398} // namespace blender::image_engine
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
MINLINE int min_ii(int a, int b)
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
void BLI_rctf_transform_calc_m3_pivot_min(const rctf *dst, const rctf *src, float matrix[3][3])
Definition rct.cc:556
bool BLI_rctf_isect(const struct rctf *src1, const struct rctf *src2, struct rctf *dest)
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition rct.cc:414
void BLI_rctf_init(struct rctf *rect, float xmin, float xmax, float ymin, float ymax)
Definition rct.cc:404
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
struct ImageTile ImageTile
struct ImageUser ImageUser
struct Image Image
struct rcti rcti
struct rctf rctf
#define GPU_ATTACHMENT_TEXTURE(_texture)
#define GPU_ATTACHMENT_NONE
void GPU_framebuffer_clear_color(blender::gpu::FrameBuffer *fb, const float clear_col[4])
void GPU_framebuffer_bind(blender::gpu::FrameBuffer *fb)
void GPU_texture_update_sub(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *pixels, int offset_x, int offset_y, int offset_z, int width, int height, int depth)
int GPU_texture_height(const blender::gpu::Texture *texture)
int GPU_texture_width(const blender::gpu::Texture *texture)
@ GPU_DATA_FLOAT
void GPU_texture_update(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *data)
void IMB_float_from_byte_ex(ImBuf *dst, const ImBuf *src, const rcti *region_to_update)
void IMB_transform(const ImBuf *src, ImBuf *dst, eIMBTransformMode mode, eIMBInterpolationFilterMode filter, const blender::float3x3 &transform_matrix, const rctf *src_crop)
Transform source image buffer onto destination image buffer using a transform matrix.
eIMBTransformMode
Transform modes to use for IMB_transform function.
Definition IMB_imbuf.hh:526
@ IMB_TRANSFORM_MODE_WRAP_REPEAT
Wrap repeat the source buffer. Only supported in with nearest filtering.
Definition IMB_imbuf.hh:532
@ IMB_TRANSFORM_MODE_CROP_SRC
Crop the source buffer.
Definition IMB_imbuf.hh:530
void IMB_free_all_data(ImBuf *ibuf)
bool IMB_initImBuf(ImBuf *ibuf, unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
@ IMB_FILTER_NEAREST
Definition IMB_imbuf.hh:281
void IMB_gpu_clamp_half_float(ImBuf *image_buffer)
Definition util_gpu.cc:419
@ IB_float_data
volatile int lock
ATTR_WARN_UNUSED_RESULT const BMVert * v
void state_set(DRWState state, int clip_plane_count=0)
detail::PassBase< command::DrawCommandBuf > Sub
Definition draw_pass.hh:499
void update_bounds(const ARegion *region) override
Update the uv and region bounds of all texture_infos of instance_data.
void ensure_texture_infos() override
Ensure enough texture infos are allocated in instance_data.
void image_sync(::Image *image, ::ImageUser *iuser) const override
static ShaderModule & module_get()
nullptr float
const DRWContext * DRW_context_get()
@ DRW_STATE_WRITE_DEPTH
Definition draw_state.hh:29
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_DEPTH_LESS_EQUAL
Definition draw_state.hh:38
@ DRW_STATE_DEPTH_ALWAYS
Definition draw_state.hh:36
@ DRW_STATE_BLEND_ALPHA_PREMUL
Definition draw_state.hh:57
#define floor
#define ceil
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
static ulong state[N]
detail::Pass< command::DrawCommandBuf > PassSimple
int tile_height(const AssetShelfSettings &settings)
int tile_width(const AssetShelfSettings &settings)
float4 interpolate_nearest_border_fl(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:27
CartesianBasis invert(const CartesianBasis &basis)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
MatBase< float, 3, 3 > float3x3
DefaultTextureList * viewport_texture_list_get() const
blender::gpu::Texture * depth
blender::gpu::Texture * color
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
ListBase tiles
float xmax
float xmin
float ymax
float ymin
int ymin
int ymax
int xmin
int xmax