Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/draw/engines/image/image_drawing_mode.hh')
-rw-r--r--source/blender/draw/engines/image/image_drawing_mode.hh417
1 files changed, 417 insertions, 0 deletions
diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh
new file mode 100644
index 00000000000..8762a02458f
--- /dev/null
+++ b/source/blender/draw/engines/image/image_drawing_mode.hh
@@ -0,0 +1,417 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright 2021, Blender Foundation.
+ */
+
+/** \file
+ * \ingroup draw_engine
+ */
+
+#pragma once
+
+#include "BKE_image_partial_update.hh"
+
+#include "IMB_imbuf_types.h"
+
+#include "image_batches.hh"
+#include "image_private.hh"
+#include "image_wrappers.hh"
+
+namespace blender::draw::image_engine {
+
+constexpr float EPSILON_UV_BOUNDS = 0.00001f;
+
+/**
+ * \brief Screen space method using a single texture spawning the whole screen.
+ */
+struct OneTextureMethod {
+ IMAGE_InstanceData *instance_data;
+
+ OneTextureMethod(IMAGE_InstanceData *instance_data) : instance_data(instance_data)
+ {
+ }
+
+ /** \brief Update the texture slot uv and screen space bounds. */
+ void update_screen_space_bounds(const ARegion *region)
+ {
+ /* Create a single texture that covers the visible screen space. */
+ BLI_rctf_init(
+ &instance_data->texture_infos[0].clipping_bounds, 0, region->winx, 0, region->winy);
+ instance_data->texture_infos[0].visible = true;
+
+ /* Mark the other textures as invalid. */
+ for (int i = 1; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
+ BLI_rctf_init_minmax(&instance_data->texture_infos[i].clipping_bounds);
+ instance_data->texture_infos[i].visible = false;
+ }
+ }
+
+ void update_uv_bounds(const ARegion *region)
+ {
+ TextureInfo &info = instance_data->texture_infos[0];
+ if (!BLI_rctf_compare(&info.uv_bounds, &region->v2d.cur, EPSILON_UV_BOUNDS)) {
+ info.uv_bounds = region->v2d.cur;
+ info.dirty = true;
+ }
+
+ /* Mark the other textures as invalid. */
+ for (int i = 1; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
+ BLI_rctf_init_minmax(&instance_data->texture_infos[i].clipping_bounds);
+ }
+ }
+};
+
+using namespace blender::bke::image::partial_update;
+
+template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractDrawingMode {
+ private:
+ DRWPass *create_image_pass() const
+ {
+ /* Write depth is needed for background overlay rendering. Near depth is used for
+ * transparency checker and Far depth is used for indicating the image size. */
+ DRWState state = static_cast<DRWState>(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
+ DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL);
+ return DRW_pass_create("Image", state);
+ }
+
+ void add_shgroups(const IMAGE_InstanceData *instance_data) const
+ {
+ const ShaderParameters &sh_params = instance_data->sh_params;
+ GPUShader *shader = IMAGE_shader_image_get(false);
+
+ DRWShadingGroup *shgrp = DRW_shgroup_create(shader, instance_data->passes.image_pass);
+ DRW_shgroup_uniform_vec2_copy(shgrp, "farNearDistances", sh_params.far_near);
+ DRW_shgroup_uniform_vec4_copy(shgrp, "color", ShaderParameters::color);
+ DRW_shgroup_uniform_vec4_copy(shgrp, "shuffle", sh_params.shuffle);
+ DRW_shgroup_uniform_int_copy(shgrp, "drawFlags", sh_params.flags);
+ DRW_shgroup_uniform_bool_copy(shgrp, "imgPremultiplied", sh_params.use_premul_alpha);
+ DRW_shgroup_uniform_vec2_copy(shgrp, "maxUv", instance_data->max_uv);
+ float image_mat[4][4];
+ unit_m4(image_mat);
+ for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
+ const TextureInfo &info = instance_data->texture_infos[i];
+ if (!info.visible) {
+ continue;
+ }
+
+ DRWShadingGroup *shgrp_sub = DRW_shgroup_create_sub(shgrp);
+ DRW_shgroup_uniform_texture_ex(shgrp_sub, "imageTexture", info.texture, GPU_SAMPLER_DEFAULT);
+ DRW_shgroup_call_obmat(shgrp_sub, info.batch, image_mat);
+ }
+ }
+
+ /**
+ * \brief Update GPUTextures for drawing the image.
+ *
+ * GPUTextures that are marked dirty are rebuild. GPUTextures that aren't marked dirty are
+ * updated with changed region of the image.
+ */
+ void update_textures(IMAGE_InstanceData &instance_data,
+ Image *image,
+ ImageUser *image_user) const
+ {
+ PartialUpdateChecker<ImageTileData> checker(
+ image, image_user, instance_data.partial_update.user);
+ PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes();
+
+ switch (changes.get_result_code()) {
+ case ePartialUpdateCollectResult::FullUpdateNeeded:
+ instance_data.mark_all_texture_slots_dirty();
+ break;
+ case ePartialUpdateCollectResult::NoChangesDetected:
+ break;
+ case ePartialUpdateCollectResult::PartialChangesDetected:
+ /* Partial update when wrap repeat is enabled is not supported. */
+ if (instance_data.flags.do_tile_drawing) {
+ instance_data.mark_all_texture_slots_dirty();
+ }
+ else {
+ do_partial_update(changes, instance_data);
+ }
+ break;
+ }
+ do_full_update_for_dirty_textures(instance_data, image_user);
+ }
+
+ void do_partial_update(PartialUpdateChecker<ImageTileData>::CollectResult &iterator,
+ IMAGE_InstanceData &instance_data) const
+ {
+ while (iterator.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
+ /* Quick exit when tile_buffer isn't availble. */
+ if (iterator.tile_data.tile_buffer == nullptr) {
+ continue;
+ }
+ ensure_float_buffer(*iterator.tile_data.tile_buffer);
+ const float tile_width = static_cast<float>(iterator.tile_data.tile_buffer->x);
+ const float tile_height = static_cast<float>(iterator.tile_data.tile_buffer->y);
+
+ for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
+ const TextureInfo &info = instance_data.texture_infos[i];
+ /* Dirty images will receive a full update. No need to do a partial one now. */
+ if (info.dirty) {
+ continue;
+ }
+ if (!info.visible) {
+ continue;
+ }
+ GPUTexture *texture = info.texture;
+ const float texture_width = GPU_texture_width(texture);
+ const float texture_height = GPU_texture_height(texture);
+ // TODO
+ // early bound check.
+ ImageTileWrapper tile_accessor(iterator.tile_data.tile);
+ float tile_offset_x = static_cast<float>(tile_accessor.get_tile_x_offset());
+ float tile_offset_y = static_cast<float>(tile_accessor.get_tile_y_offset());
+ rcti *changed_region_in_texel_space = &iterator.changed_region.region;
+ rctf changed_region_in_uv_space;
+ BLI_rctf_init(&changed_region_in_uv_space,
+ static_cast<float>(changed_region_in_texel_space->xmin) /
+ static_cast<float>(iterator.tile_data.tile_buffer->x) +
+ tile_offset_x,
+ static_cast<float>(changed_region_in_texel_space->xmax) /
+ static_cast<float>(iterator.tile_data.tile_buffer->x) +
+ tile_offset_x,
+ static_cast<float>(changed_region_in_texel_space->ymin) /
+ static_cast<float>(iterator.tile_data.tile_buffer->y) +
+ tile_offset_y,
+ static_cast<float>(changed_region_in_texel_space->ymax) /
+ static_cast<float>(iterator.tile_data.tile_buffer->y) +
+ tile_offset_y);
+ rctf changed_overlapping_region_in_uv_space;
+ const bool region_overlap = BLI_rctf_isect(
+ &info.uv_bounds, &changed_region_in_uv_space, &changed_overlapping_region_in_uv_space);
+ if (!region_overlap) {
+ continue;
+ }
+ // convert the overlapping region to texel space and to ss_pixel space...
+ // TODO: first convert to ss_pixel space as integer based. and from there go back to texel
+ // space. But perhaps this isn't needed and we could use an extraction offset somehow.
+ rcti gpu_texture_region_to_update;
+ BLI_rcti_init(&gpu_texture_region_to_update,
+ floor((changed_overlapping_region_in_uv_space.xmin - info.uv_bounds.xmin) *
+ texture_width / BLI_rctf_size_x(&info.uv_bounds)),
+ floor((changed_overlapping_region_in_uv_space.xmax - info.uv_bounds.xmin) *
+ texture_width / BLI_rctf_size_x(&info.uv_bounds)),
+ ceil((changed_overlapping_region_in_uv_space.ymin - info.uv_bounds.ymin) *
+ texture_height / BLI_rctf_size_y(&info.uv_bounds)),
+ ceil((changed_overlapping_region_in_uv_space.ymax - info.uv_bounds.ymin) *
+ texture_height / BLI_rctf_size_y(&info.uv_bounds)));
+
+ rcti tile_region_to_extract;
+ BLI_rcti_init(
+ &tile_region_to_extract,
+ floor((changed_overlapping_region_in_uv_space.xmin - tile_offset_x) * tile_width),
+ floor((changed_overlapping_region_in_uv_space.xmax - tile_offset_x) * tile_width),
+ ceil((changed_overlapping_region_in_uv_space.ymin - tile_offset_y) * tile_height),
+ ceil((changed_overlapping_region_in_uv_space.ymax - tile_offset_y) * tile_height));
+
+ // Create an image buffer with a size
+ // extract and scale into an imbuf
+ const int texture_region_width = BLI_rcti_size_x(&gpu_texture_region_to_update);
+ const int texture_region_height = BLI_rcti_size_y(&gpu_texture_region_to_update);
+
+ ImBuf extracted_buffer;
+ IMB_initImBuf(
+ &extracted_buffer, texture_region_width, texture_region_height, 32, IB_rectfloat);
+
+ int offset = 0;
+ ImBuf *tile_buffer = iterator.tile_data.tile_buffer;
+ for (int y = gpu_texture_region_to_update.ymin; y < gpu_texture_region_to_update.ymax;
+ y++) {
+ float yf = y / (float)texture_height;
+ float v = info.uv_bounds.ymax * yf + info.uv_bounds.ymin * (1.0 - yf) - tile_offset_y;
+ for (int x = gpu_texture_region_to_update.xmin; x < gpu_texture_region_to_update.xmax;
+ x++) {
+ float xf = x / (float)texture_width;
+ float u = info.uv_bounds.xmax * xf + info.uv_bounds.xmin * (1.0 - xf) - tile_offset_x;
+ nearest_interpolation_color(tile_buffer,
+ nullptr,
+ &extracted_buffer.rect_float[offset * 4],
+ u * tile_buffer->x,
+ v * tile_buffer->y);
+ offset++;
+ }
+ }
+
+ GPU_texture_update_sub(texture,
+ GPU_DATA_FLOAT,
+ extracted_buffer.rect_float,
+ gpu_texture_region_to_update.xmin,
+ gpu_texture_region_to_update.ymin,
+ 0,
+ extracted_buffer.x,
+ extracted_buffer.y,
+ 0);
+ imb_freerectImbuf_all(&extracted_buffer);
+ }
+ }
+ }
+
+ void do_full_update_for_dirty_textures(IMAGE_InstanceData &instance_data,
+ const ImageUser *image_user) const
+ {
+ for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
+ TextureInfo &info = instance_data.texture_infos[i];
+ if (!info.dirty) {
+ continue;
+ }
+ if (!info.visible) {
+ continue;
+ }
+ do_full_update_gpu_texture(info, instance_data, image_user);
+ }
+ }
+
+ void do_full_update_gpu_texture(TextureInfo &info,
+ IMAGE_InstanceData &instance_data,
+ const ImageUser *image_user) const
+ {
+
+ ImBuf texture_buffer;
+ const int texture_width = GPU_texture_width(info.texture);
+ const int texture_height = GPU_texture_height(info.texture);
+ IMB_initImBuf(&texture_buffer, texture_width, texture_height, 0, IB_rectfloat);
+ ImageUser tile_user = {0};
+ if (image_user) {
+ tile_user = *image_user;
+ }
+
+ void *lock;
+
+ Image *image = instance_data.image;
+ LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
+ const ImageTileWrapper image_tile(image_tile_ptr);
+ tile_user.tile = image_tile.get_tile_number();
+
+ ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, &lock);
+ if (tile_buffer == nullptr) {
+ /* Couldn't load the image buffer of the tile. */
+ continue;
+ }
+ do_full_update_texture_slot(instance_data, info, texture_buffer, *tile_buffer, image_tile);
+ BKE_image_release_ibuf(image, tile_buffer, lock);
+ }
+ GPU_texture_update(info.texture, GPU_DATA_FLOAT, texture_buffer.rect_float);
+ imb_freerectImbuf_all(&texture_buffer);
+ }
+
+ /**
+ * \brief Ensure that the float buffer of the given image buffer is available.
+ */
+ void ensure_float_buffer(ImBuf &image_buffer) const
+ {
+ if (image_buffer.rect_float == nullptr) {
+ IMB_float_from_rect(&image_buffer);
+ }
+ }
+
+ void do_full_update_texture_slot(const IMAGE_InstanceData &instance_data,
+ const TextureInfo &texture_info,
+ ImBuf &texture_buffer,
+ ImBuf &tile_buffer,
+ const ImageTileWrapper &image_tile) const
+ {
+ const int texture_width = texture_buffer.x;
+ const int texture_height = texture_buffer.y;
+ ensure_float_buffer(tile_buffer);
+
+ /* IMB_transform works in a non-consistent space. This should be documented or fixed!.
+ * Construct a variant of the info_uv_to_texture that adds the texel space
+ * transformation.*/
+ float uv_to_texel[4][4];
+ copy_m4_m4(uv_to_texel, instance_data.ss_to_texture);
+ float scale[3] = {static_cast<float>(texture_width) / static_cast<float>(tile_buffer.x),
+ static_cast<float>(texture_height) / static_cast<float>(tile_buffer.y),
+ 1.0f};
+ rescale_m4(uv_to_texel, scale);
+ uv_to_texel[3][0] += image_tile.get_tile_x_offset() / BLI_rctf_size_x(&texture_info.uv_bounds);
+ uv_to_texel[3][1] += image_tile.get_tile_y_offset() / BLI_rctf_size_y(&texture_info.uv_bounds);
+ uv_to_texel[3][0] *= texture_width;
+ uv_to_texel[3][1] *= texture_height;
+ invert_m4(uv_to_texel);
+
+ rctf crop_rect;
+ rctf *crop_rect_ptr = nullptr;
+ eIMBTransformMode transform_mode;
+ if (instance_data.flags.do_tile_drawing) {
+ transform_mode = IMB_TRANSFORM_MODE_WRAP_REPEAT;
+ }
+ else {
+ BLI_rctf_init(&crop_rect, 0.0, tile_buffer.x, 0.0, tile_buffer.y);
+ crop_rect_ptr = &crop_rect;
+ transform_mode = IMB_TRANSFORM_MODE_CROP_SRC;
+ }
+
+ IMB_transform(&tile_buffer,
+ &texture_buffer,
+ transform_mode,
+ IMB_FILTER_NEAREST,
+ uv_to_texel,
+ crop_rect_ptr);
+ }
+
+ public:
+ void cache_init(IMAGE_Data *vedata) const override
+ {
+ IMAGE_InstanceData *instance_data = vedata->instance_data;
+ instance_data->passes.image_pass = create_image_pass();
+ }
+
+ void cache_image(IMAGE_Data *vedata, Image *image, ImageUser *iuser) const override
+ {
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ IMAGE_InstanceData *instance_data = vedata->instance_data;
+ TextureMethod method(instance_data);
+
+ instance_data->partial_update.ensure_image(image);
+ instance_data->max_uv_update();
+ instance_data->clear_dirty_flag();
+
+ // Step: Find out which screen space textures are needed to draw on the screen. Remove the
+ // screen space textures that aren't needed.
+ const ARegion *region = draw_ctx->region;
+ method.update_screen_space_bounds(region);
+ method.update_uv_bounds(region);
+
+ // Step: Update the GPU textures based on the changes in the image.
+ instance_data->update_gpu_texture_allocations();
+ update_textures(*instance_data, image, iuser);
+
+ // Step: Add the GPU textures to the shgroup.
+ instance_data->update_batches();
+ add_shgroups(instance_data);
+ }
+
+ void draw_finish(IMAGE_Data *UNUSED(vedata)) const override
+ {
+ }
+
+ void draw_scene(IMAGE_Data *vedata) const override
+ {
+ IMAGE_InstanceData *instance_data = vedata->instance_data;
+
+ DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+ GPU_framebuffer_bind(dfbl->default_fb);
+ static float clear_col[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ GPU_framebuffer_clear_color_depth(dfbl->default_fb, clear_col, 1.0);
+
+ DRW_view_set_active(instance_data->view);
+ DRW_draw_pass(instance_data->passes.image_pass);
+ DRW_view_set_active(nullptr);
+ }
+}; // namespace clipping
+
+} // namespace blender::draw::image_engine