DrawManager: Image engine support huge images.

Adding better support for drawing huge images in the image/uv editor. Also solved tearing artifacts.
The approach is that for each image/uv editor a screen space gpu texture is created that only contains
the visible pixels. When zooming or panning the gpu texture is rebuild.

Although the solution isn't memory intensive other parts of blender memory usage scales together with
the image size.

* Due to complexity we didn't implement partial updates when drawing images tiled (wrap repeat).
  This could be added, but is complicated as a change in the source could mean many different
  changes on the GPU texture. The work around for now is to tag all gpu textures to be dirty when
  changes are detected.

Original plan was to have 4 screen space images to support panning without gpu texture creation.
For now we don't see the need to implement it as the solution is already fast. Especially when
GPU memory is shared with CPU ram.

Reviewed By: fclem

Maniphest Tasks: T92525, T92903

Differential Revision: https://developer.blender.org/D13424
This commit is contained in:
Jeroen Bakker 2022-01-28 08:37:12 +01:00 committed by Jeroen Bakker
parent 0a32ac02e9
commit bdd74e1e93
Notes: blender-bot 2023-02-14 03:44:41 +01:00
Referenced by issue #98350, Regression: Crashes when painting on an image sequence with clone tool
Referenced by issue #95871, Non float images have float buffers if displayed in the Image Editor once
Referenced by issue #95731, Image viewer in blender is not scaling to 100% properly
Referenced by issue #95332, Some old scenes crashes in Blender 3.2
Referenced by issue #95299, Render Result view is drawn with transparent image instead of opaque grid
Referenced by issue #95298, Regression: Multi-view images fail to display properly in the Image Editor
Referenced by issue #92903, Artifacts in image editor
Referenced by issue #92525, Image Engine redesign.
Referenced by issue #92350, Slow performance in different slots in the Image Editor
Referenced by issue #83529, Horizontal stripe artifacts in image editor
Referenced by issue #80113, Support Huge Image Sizes in image/uv editor
20 changed files with 1173 additions and 435 deletions

View File

@ -295,4 +295,4 @@ template<typename TileData = NoTileData> struct PartialUpdateChecker {
};
} // namespace partial_update
} // namespace blender::bke::image
} // namespace blender::bke::image

View File

@ -58,14 +58,6 @@ static void image_free_gpu_limited_scale(Image *ima);
static void image_update_gputexture_ex(
Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h);
/* Internal structs. */
#define IMA_PARTIAL_REFRESH_TILE_SIZE 256
struct ImagePartialRefresh {
struct ImagePartialRefresh *next, *prev;
int tile_x;
int tile_y;
};
bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf)
{
if (image) {

View File

@ -404,6 +404,7 @@ void invert_m4_m4_safe_ortho(float Ainv[4][4], const float A[4][4]);
void scale_m3_fl(float R[3][3], float scale);
void scale_m4_fl(float R[4][4], float scale);
void scale_m4_v2(float R[4][4], const float scale[2]);
/**
* This computes the overall volume scale factor of a transformation matrix.

View File

@ -2296,6 +2296,17 @@ void scale_m4_fl(float R[4][4], float scale)
R[3][0] = R[3][1] = R[3][2] = 0.0;
}
void scale_m4_v2(float R[4][4], const float scale[2])
{
R[0][0] = scale[0];
R[1][1] = scale[1];
R[2][2] = R[3][3] = 1.0;
R[0][1] = R[0][2] = R[0][3] = 0.0;
R[1][0] = R[1][2] = R[1][3] = 0.0;
R[2][0] = R[2][1] = R[2][3] = 0.0;
R[3][0] = R[3][1] = R[3][2] = 0.0;
}
void translate_m4(float mat[4][4], float Tx, float Ty, float Tz)
{
mat[3][0] += (Tx * mat[0][0] + Ty * mat[1][0] + Tz * mat[2][0]);

View File

@ -227,11 +227,17 @@ set(SRC
engines/eevee/eevee_lut.h
engines/eevee/eevee_private.h
engines/external/external_engine.h
engines/image/image_drawing_mode_image_space.hh
engines/image/image_batches.hh
engines/image/image_drawing_mode.hh
engines/image/image_engine.h
engines/image/image_instance_data.hh
engines/image/image_partial_updater.hh
engines/image/image_private.hh
engines/image/image_shader_params.hh
engines/image/image_space_image.hh
engines/image/image_space_node.hh
engines/image/image_space.hh
engines/image/image_wrappers.hh
engines/workbench/workbench_engine.h
engines/workbench/workbench_private.h
engines/workbench/workbench_shader_shared.h

View File

@ -0,0 +1,106 @@
/*
* 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 "image_texture_info.hh"
/** \brief Create GPUBatch for a IMAGE_ScreenSpaceTextureInfo. */
class BatchUpdater {
TextureInfo &info;
GPUVertFormat format = {0};
int pos_id;
int uv_id;
public:
BatchUpdater(TextureInfo &info) : info(info)
{
}
void update_batch()
{
ensure_clear_batch();
ensure_format();
init_batch();
}
void discard_batch()
{
GPU_BATCH_DISCARD_SAFE(info.batch);
}
private:
void ensure_clear_batch()
{
GPU_BATCH_CLEAR_SAFE(info.batch);
if (info.batch == nullptr) {
info.batch = GPU_batch_calloc();
}
}
void init_batch()
{
GPUVertBuf *vbo = create_vbo();
GPU_batch_init_ex(info.batch, GPU_PRIM_TRI_FAN, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
GPUVertBuf *create_vbo()
{
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(vbo, 4);
float pos[4][2];
fill_tri_fan_from_rctf(pos, info.clipping_bounds);
float uv[4][2];
fill_tri_fan_from_rctf(uv, info.uv_bounds);
for (int i = 0; i < 4; i++) {
GPU_vertbuf_attr_set(vbo, pos_id, i, pos[i]);
GPU_vertbuf_attr_set(vbo, uv_id, i, uv[i]);
}
return vbo;
}
static void fill_tri_fan_from_rctf(float result[4][2], rctf &rect)
{
result[0][0] = rect.xmin;
result[0][1] = rect.ymin;
result[1][0] = rect.xmax;
result[1][1] = rect.ymin;
result[2][0] = rect.xmax;
result[2][1] = rect.ymax;
result[3][0] = rect.xmin;
result[3][1] = rect.ymax;
}
void ensure_format()
{
if (format.attr_len == 0) {
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
GPU_vertformat_attr_add(&format, "uv", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
pos_id = GPU_vertformat_attr_id_get(&format, "pos");
uv_id = GPU_vertformat_attr_id_get(&format, "uv");
}
}
};

View File

@ -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

View File

@ -1,147 +0,0 @@
/*
* 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 "image_private.hh"
namespace blender::draw::image_engine {
class ImageSpaceDrawingMode : 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_to_shgroup(AbstractSpaceAccessor *space,
DRWShadingGroup *grp,
const Image *image,
const ImBuf *image_buffer) const
{
float image_mat[4][4];
const DRWContextState *draw_ctx = DRW_context_state_get();
const ARegion *region = draw_ctx->region;
space->get_image_mat(image_buffer, region, image_mat);
GPUBatch *geom = DRW_cache_quad_get();
const float translate_x = image_mat[3][0];
const float translate_y = image_mat[3][1];
LISTBASE_FOREACH (ImageTile *, tile, &image->tiles) {
const int tile_x = ((tile->tile_number - 1001) % 10);
const int tile_y = ((tile->tile_number - 1001) / 10);
image_mat[3][0] = (float)tile_x + translate_x;
image_mat[3][1] = (float)tile_y + translate_y;
DRW_shgroup_call_obmat(grp, geom, image_mat);
}
}
public:
void cache_init(IMAGE_Data *vedata) const override
{
IMAGE_PassList *psl = vedata->psl;
psl->image_pass = create_image_pass();
}
void cache_image(AbstractSpaceAccessor *space,
IMAGE_Data *vedata,
Image *image,
ImageUser *iuser,
ImBuf *image_buffer) const override
{
IMAGE_PassList *psl = vedata->psl;
IMAGE_StorageList *stl = vedata->stl;
IMAGE_PrivateData *pd = stl->pd;
GPUTexture *tex_tile_data = nullptr;
space->get_gpu_textures(
image, iuser, image_buffer, &pd->texture, &pd->owns_texture, &tex_tile_data);
if (pd->texture == nullptr) {
return;
}
const bool is_tiled_texture = tex_tile_data != nullptr;
ShaderParameters sh_params;
sh_params.use_premul_alpha = BKE_image_has_gpu_texture_premultiplied_alpha(image,
image_buffer);
const DRWContextState *draw_ctx = DRW_context_state_get();
const Scene *scene = draw_ctx->scene;
if (scene->camera && scene->camera->type == OB_CAMERA) {
Camera *camera = static_cast<Camera *>(scene->camera->data);
copy_v2_fl2(sh_params.far_near, camera->clip_end, camera->clip_start);
}
space->get_shader_parameters(sh_params, image_buffer, is_tiled_texture);
GPUShader *shader = IMAGE_shader_image_get(is_tiled_texture);
DRWShadingGroup *shgrp = DRW_shgroup_create(shader, psl->image_pass);
if (is_tiled_texture) {
DRW_shgroup_uniform_texture_ex(shgrp, "imageTileArray", pd->texture, GPU_SAMPLER_DEFAULT);
DRW_shgroup_uniform_texture(shgrp, "imageTileData", tex_tile_data);
}
else {
DRW_shgroup_uniform_texture_ex(shgrp, "imageTexture", pd->texture, GPU_SAMPLER_DEFAULT);
}
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);
add_to_shgroup(space, shgrp, image, image_buffer);
}
void draw_finish(IMAGE_Data *vedata) const override
{
IMAGE_StorageList *stl = vedata->stl;
IMAGE_PrivateData *pd = stl->pd;
if (pd->texture && pd->owns_texture) {
GPU_texture_free(pd->texture);
pd->owns_texture = false;
}
pd->texture = nullptr;
}
void draw_scene(IMAGE_Data *vedata) const override
{
IMAGE_PassList *psl = vedata->psl;
IMAGE_PrivateData *pd = vedata->stl->pd;
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(pd->view);
DRW_draw_pass(psl->image_pass);
DRW_view_set_active(nullptr);
}
};
} // namespace blender::draw::image_engine

View File

@ -41,7 +41,7 @@
#include "GPU_batch.h"
#include "image_drawing_mode_image_space.hh"
#include "image_drawing_mode.hh"
#include "image_engine.h"
#include "image_private.hh"
#include "image_space_image.hh"
@ -68,7 +68,7 @@ template<
*
* Useful during development to switch between drawing implementations.
*/
typename DrawingMode = ImageSpaceDrawingMode>
typename DrawingMode = ScreenSpaceDrawingMode<OneTextureMethod>>
class ImageEngine {
private:
const DRWContextState *draw_ctx;
@ -86,48 +86,58 @@ class ImageEngine {
void cache_init()
{
IMAGE_StorageList *stl = vedata->stl;
IMAGE_PrivateData *pd = stl->pd;
IMAGE_InstanceData *instance_data = vedata->instance_data;
drawing_mode.cache_init(vedata);
pd->view = nullptr;
if (space->has_view_override()) {
const ARegion *region = draw_ctx->region;
pd->view = space->create_view_override(region);
}
/* Setup full screen view matrix. */
const ARegion *region = draw_ctx->region;
float winmat[4][4], viewmat[4][4];
orthographic_m4(viewmat, 0.0, region->winx, 0.0, region->winy, 0.0, 1.0);
unit_m4(winmat);
instance_data->view = DRW_view_create(viewmat, winmat, nullptr, nullptr, nullptr);
}
void cache_populate()
{
IMAGE_StorageList *stl = vedata->stl;
IMAGE_PrivateData *pd = stl->pd;
IMAGE_InstanceData *instance_data = vedata->instance_data;
Main *bmain = CTX_data_main(draw_ctx->evil_C);
pd->image = space->get_image(bmain);
if (pd->image == nullptr) {
instance_data->image = space->get_image(bmain);
if (instance_data->image == nullptr) {
/* Early exit, nothing to draw. */
return;
}
pd->ibuf = space->acquire_image_buffer(pd->image, &pd->lock);
instance_data->flags.do_tile_drawing = instance_data->image->source != IMA_SRC_TILED &&
space->use_tile_drawing();
void *lock;
ImBuf *image_buffer = space->acquire_image_buffer(instance_data->image, &lock);
/* Setup the matrix to go from screen UV coordinates to UV texture space coordinates. */
float image_resolution[2] = {image_buffer ? image_buffer->x : 1024.0f,
image_buffer ? image_buffer->y : 1024.0f};
space->init_ss_to_texture_matrix(
draw_ctx->region, image_resolution, instance_data->ss_to_texture);
const Scene *scene = DRW_context_state_get()->scene;
instance_data->sh_params.update(space.get(), scene, instance_data->image, image_buffer);
space->release_buffer(instance_data->image, image_buffer, lock);
ImageUser *iuser = space->get_image_user();
drawing_mode.cache_image(space.get(), vedata, pd->image, iuser, pd->ibuf);
drawing_mode.cache_image(vedata, instance_data->image, iuser);
}
void draw_finish()
{
drawing_mode.draw_finish(vedata);
IMAGE_StorageList *stl = vedata->stl;
IMAGE_PrivateData *pd = stl->pd;
space->release_buffer(pd->image, pd->ibuf, pd->lock);
pd->image = nullptr;
pd->ibuf = nullptr;
IMAGE_InstanceData *instance_data = vedata->instance_data;
instance_data->image = nullptr;
}
void draw_scene()
{
drawing_mode.draw_scene(vedata);
}
};
}; // namespace blender::draw::image_engine
/* -------------------------------------------------------------------- */
/** \name Engine Callbacks
@ -137,15 +147,9 @@ static void IMAGE_engine_init(void *ved)
{
IMAGE_shader_library_ensure();
IMAGE_Data *vedata = (IMAGE_Data *)ved;
IMAGE_StorageList *stl = vedata->stl;
if (!stl->pd) {
stl->pd = static_cast<IMAGE_PrivateData *>(MEM_callocN(sizeof(IMAGE_PrivateData), __func__));
if (vedata->instance_data == nullptr) {
vedata->instance_data = MEM_new<IMAGE_InstanceData>(__func__);
}
IMAGE_PrivateData *pd = stl->pd;
pd->ibuf = nullptr;
pd->lock = nullptr;
pd->texture = nullptr;
}
static void IMAGE_cache_init(void *vedata)
@ -174,6 +178,12 @@ static void IMAGE_engine_free()
IMAGE_shader_free();
}
static void IMAGE_instance_free(void *_instance_data)
{
IMAGE_InstanceData *instance_data = reinterpret_cast<IMAGE_InstanceData *>(_instance_data);
MEM_delete(instance_data);
}
/** \} */
static const DrawEngineDataSize IMAGE_data_size = DRW_VIEWPORT_DATA_SIZE(IMAGE_Data);
@ -191,7 +201,7 @@ DrawEngineType draw_engine_image_type = {
&IMAGE_data_size, /* vedata_size */
&IMAGE_engine_init, /* engine_init */
&IMAGE_engine_free, /* engine_free */
nullptr, /* instance_free */
&IMAGE_instance_free, /* instance_free */
&IMAGE_cache_init, /* cache_init */
&IMAGE_cache_populate, /* cache_populate */
nullptr, /* cache_finish */

View File

@ -0,0 +1,134 @@
/*
* 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 "image_batches.hh"
#include "image_partial_updater.hh"
#include "image_private.hh"
#include "image_shader_params.hh"
#include "image_texture_info.hh"
#include "image_wrappers.hh"
#include "DRW_render.h"
/**
* \brief max allowed textures to use by the ScreenSpaceDrawingMode.
*
* 4 textures are used to reduce uploading screen space textures when translating the image.
*/
constexpr int SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN = 4;
struct IMAGE_InstanceData {
struct Image *image;
PartialImageUpdater partial_update;
struct DRWView *view;
ShaderParameters sh_params;
struct {
/**
* \brief should we perform tiled drawing (wrap repeat).
*
* Option is true when image is capable of tile drawing (image is not tile) and the tiled
* option is set in the space.
*/
bool do_tile_drawing : 1;
} flags;
struct {
DRWPass *image_pass;
} passes;
/** \brief Transform matrix to convert a normalized screen space coordinates to texture space. */
float ss_to_texture[4][4];
TextureInfo texture_infos[SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN];
/**
* \brief Maximum uv's that are on the border of the image.
*
* Larger UV coordinates would be drawn as a border. */
float max_uv[2];
public:
void max_uv_update()
{
copy_v2_fl2(max_uv, 1.0f, 1.0);
LISTBASE_FOREACH (ImageTile *, image_tile_ptr, &image->tiles) {
ImageTileWrapper image_tile(image_tile_ptr);
max_uv[0] = max_ii(max_uv[0], image_tile.get_tile_x_offset() + 1);
max_uv[1] = max_ii(max_uv[1], image_tile.get_tile_y_offset() + 1);
}
}
void clear_dirty_flag()
{
reset_dirty_flag(false);
}
void mark_all_texture_slots_dirty()
{
reset_dirty_flag(true);
}
void update_gpu_texture_allocations()
{
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
TextureInfo &info = texture_infos[i];
const bool is_allocated = info.texture != nullptr;
const bool is_visible = info.visible;
const bool should_be_freed = !is_visible && is_allocated;
const bool should_be_created = is_visible && !is_allocated;
if (should_be_freed) {
GPU_texture_free(info.texture);
info.texture = nullptr;
}
if (should_be_created) {
DRW_texture_ensure_fullscreen_2d(
&info.texture, GPU_RGBA16F, static_cast<DRWTextureFlag>(0));
}
info.dirty |= should_be_created;
}
}
void update_batches()
{
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
TextureInfo &info = texture_infos[i];
if (!info.dirty) {
continue;
}
BatchUpdater batch_updater(info);
batch_updater.update_batch();
}
}
private:
/** \brief Set dirty flag of all texture slots to the given value. */
void reset_dirty_flag(bool new_value)
{
for (int i = 0; i < SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN; i++) {
texture_infos[i].dirty = new_value;
}
}
};

View File

@ -0,0 +1,78 @@
/*
* 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.h"
#include "BKE_image_partial_update.hh"
struct PartialImageUpdater {
struct PartialUpdateUser *user;
const struct Image *image;
/**
* \brief Ensure that there is a partial update user for the given image.
*/
void ensure_image(const Image *image)
{
if (!is_valid(image)) {
free();
create(image);
}
}
virtual ~PartialImageUpdater()
{
free();
}
private:
/**
* \brief check if the partial update user can still be used for the given image.
*
* When switching to a different image the partial update user should be recreated.
*/
bool is_valid(const Image *new_image) const
{
if (image != new_image) {
return false;
}
return user != nullptr;
}
void create(const Image *image)
{
BLI_assert(user == nullptr);
user = BKE_image_partial_update_create(image);
image = image;
}
void free()
{
if (user != nullptr) {
BKE_image_partial_update_free(user);
user = nullptr;
image = nullptr;
}
}
};

View File

@ -24,6 +24,11 @@
#include <optional>
#include "BKE_image.h"
#include "image_instance_data.hh"
#include "image_texture_info.hh"
/* Forward declarations */
extern "C" {
struct GPUTexture;
@ -35,32 +40,13 @@ struct Image;
namespace blender::draw::image_engine {
/* GPUViewport.storage
* Is freed every time the viewport engine changes. */
struct IMAGE_PassList {
DRWPass *image_pass;
};
struct IMAGE_PrivateData {
void *lock;
struct ImBuf *ibuf;
struct Image *image;
struct DRWView *view;
struct GPUTexture *texture;
bool owns_texture;
};
struct IMAGE_StorageList {
IMAGE_PrivateData *pd;
};
struct IMAGE_Data {
void *engine_type;
DRWViewportEmptyList *fbl;
DRWViewportEmptyList *txl;
IMAGE_PassList *psl;
IMAGE_StorageList *stl;
DRWViewportEmptyList *psl;
DRWViewportEmptyList *stl;
IMAGE_InstanceData *instance_data;
};
/* Shader parameters. */
@ -69,105 +55,6 @@ struct IMAGE_Data {
#define IMAGE_DRAW_FLAG_SHUFFLING (1 << 2)
#define IMAGE_DRAW_FLAG_DEPTH (1 << 3)
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
#define IMAGE_DRAW_FLAG_USE_WORLD_POS (1 << 5)
struct ShaderParameters {
constexpr static float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
int flags = 0;
float shuffle[4];
float far_near[2];
bool use_premul_alpha = false;
ShaderParameters()
{
copy_v4_fl(shuffle, 1.0f);
copy_v2_fl2(far_near, 100.0f, 0.0f);
}
};
/**
* Space accessor.
*
* Image engine is used to draw the images inside multiple spaces \see SpaceLink.
* The AbstractSpaceAccessor is an interface to communicate with a space.
*/
class AbstractSpaceAccessor {
public:
virtual ~AbstractSpaceAccessor() = default;
/**
* Return the active image of the space.
*
* The returned image will be drawn in the space.
*
* The return value is optional.
*/
virtual Image *get_image(Main *bmain) = 0;
/**
* Return the #ImageUser of the space.
*
* The return value is optional.
*/
virtual ImageUser *get_image_user() = 0;
/**
* Acquire the image buffer of the image.
*
* \param image: Image to get the buffer from. Image is the same as returned from the #get_image
* member.
* \param lock: pointer to a lock object.
* \return Image buffer of the given image.
*/
virtual ImBuf *acquire_image_buffer(Image *image, void **lock) = 0;
/**
* Release a previous locked image from #acquire_image_buffer.
*/
virtual void release_buffer(Image *image, ImBuf *image_buffer, void *lock) = 0;
/**
* Update the r_shader_parameters with space specific settings.
*
* Only update the #ShaderParameters.flags and #ShaderParameters.shuffle. Other parameters
* are updated inside the image engine.
*/
virtual void get_shader_parameters(ShaderParameters &r_shader_parameters,
ImBuf *image_buffer,
bool is_tiled) = 0;
/**
* Retrieve the gpu textures to draw.
*/
virtual void get_gpu_textures(Image *image,
ImageUser *iuser,
ImBuf *image_buffer,
GPUTexture **r_gpu_texture,
bool *r_owns_texture,
GPUTexture **r_tex_tile_data) = 0;
/**
* Does this space override the view.
* When so this member should return true and the create_view_override must return the view to
* use during drawing.
*/
virtual bool has_view_override() const = 0;
/**
* Override the view for drawing.
* Should match #has_view_override.
*/
virtual DRWView *create_view_override(const ARegion *UNUSED(region)) = 0;
/**
* Initialize the matrix that will be used to draw the image. The matrix will be send as object
* matrix to the drawing pipeline.
*/
virtual void get_image_mat(const ImBuf *image_buffer,
const ARegion *region,
float r_mat[4][4]) const = 0;
}; // namespace blender::draw::image_engine
/**
* Abstract class for a drawing mode of the image engine.
@ -179,11 +66,7 @@ class AbstractDrawingMode {
public:
virtual ~AbstractDrawingMode() = default;
virtual void cache_init(IMAGE_Data *vedata) const = 0;
virtual void cache_image(AbstractSpaceAccessor *space,
IMAGE_Data *vedata,
Image *image,
ImageUser *iuser,
ImBuf *image_buffer) const = 0;
virtual void cache_image(IMAGE_Data *vedata, Image *image, ImageUser *iuser) const = 0;
virtual void draw_scene(IMAGE_Data *vedata) const = 0;
virtual void draw_finish(IMAGE_Data *vedata) const = 0;
};

View File

@ -0,0 +1,59 @@
/*
* 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 "DNA_camera_types.h"
#include "DNA_image_types.h"
#include "DNA_scene_types.h"
#include "IMB_imbuf_types.h"
#include "BKE_image.h"
#include "BLI_math.h"
#include "image_space.hh"
struct ShaderParameters {
constexpr static float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
int flags = 0;
float shuffle[4];
float far_near[2];
bool use_premul_alpha = false;
void update(AbstractSpaceAccessor *space, const Scene *scene, Image *image, ImBuf *image_buffer)
{
copy_v4_fl(shuffle, 1.0f);
copy_v2_fl2(far_near, 100.0f, 0.0f);
use_premul_alpha = BKE_image_has_gpu_texture_premultiplied_alpha(image, image_buffer);
if (scene->camera && scene->camera->type == OB_CAMERA) {
Camera *camera = static_cast<Camera *>(scene->camera->data);
copy_v2_fl2(far_near, camera->clip_end, camera->clip_start);
}
const bool is_tiled_image = (image->source == IMA_SRC_TILED);
space->get_shader_parameters(*this, image_buffer, is_tiled_image);
}
};

View File

@ -0,0 +1,99 @@
/*
* 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
class ShaderParameters;
/**
* Space accessor.
*
* Image engine is used to draw the images inside multiple spaces \see SpaceLink.
* The AbstractSpaceAccessor is an interface to communicate with a space.
*/
class AbstractSpaceAccessor {
public:
virtual ~AbstractSpaceAccessor() = default;
/**
* Return the active image of the space.
*
* The returned image will be drawn in the space.
*
* The return value is optional.
*/
virtual Image *get_image(Main *bmain) = 0;
/**
* Return the #ImageUser of the space.
*
* The return value is optional.
*/
virtual ImageUser *get_image_user() = 0;
/**
* Acquire the image buffer of the image.
*
* \param image: Image to get the buffer from. Image is the same as returned from the #get_image
* member.
* \param lock: pointer to a lock object.
* \return Image buffer of the given image.
*/
virtual ImBuf *acquire_image_buffer(Image *image, void **lock) = 0;
/**
* Release a previous locked image from #acquire_image_buffer.
*/
virtual void release_buffer(Image *image, ImBuf *image_buffer, void *lock) = 0;
/**
* Update the r_shader_parameters with space specific settings.
*
* Only update the #ShaderParameters.flags and #ShaderParameters.shuffle. Other parameters
* are updated inside the image engine.
*/
virtual void get_shader_parameters(ShaderParameters &r_shader_parameters,
ImBuf *image_buffer,
bool is_tiled) = 0;
/**
* Retrieve the gpu textures to draw.
*/
virtual void get_gpu_textures(Image *image,
ImageUser *iuser,
ImBuf *image_buffer,
GPUTexture **r_gpu_texture,
bool *r_owns_texture,
GPUTexture **r_tex_tile_data) = 0;
/** \brief Is (wrap) repeat option enabled in the space. */
virtual bool use_tile_drawing() const = 0;
/**
* \brief Initialize r_uv_to_texture matrix to transform from normalized screen space coordinates
* (0..1) to texture space UV coordinates.
*/
virtual void init_ss_to_texture_matrix(const ARegion *region,
const float image_resolution[2],
float r_uv_to_texture[4][4]) const = 0;
}; // namespace blender::draw::image_engine

View File

@ -61,7 +61,6 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
const int sima_flag = sima->flag & ED_space_image_get_display_channel_mask(image_buffer);
const bool do_repeat = (!is_tiled) && ((sima->flag & SI_DRAW_TILE) != 0);
SET_FLAG_FROM_TEST(r_shader_parameters.flags, do_repeat, IMAGE_DRAW_FLAG_DO_REPEAT);
SET_FLAG_FROM_TEST(r_shader_parameters.flags, is_tiled, IMAGE_DRAW_FLAG_USE_WORLD_POS);
if ((sima_flag & SI_USE_ALPHA) != 0) {
/* Show RGBA */
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHOW_ALPHA | IMAGE_DRAW_FLAG_APPLY_ALPHA;
@ -102,15 +101,6 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
}
}
bool has_view_override() const override
{
return false;
}
DRWView *create_view_override(const ARegion *UNUSED(region)) override
{
return nullptr;
}
void get_gpu_textures(Image *image,
ImageUser *iuser,
ImBuf *image_buffer,
@ -171,11 +161,25 @@ class SpaceImageAccessor : public AbstractSpaceAccessor {
}
}
void get_image_mat(const ImBuf *UNUSED(image_buffer),
const ARegion *UNUSED(region),
float r_mat[4][4]) const override
bool use_tile_drawing() const override
{
unit_m4(r_mat);
return (sima->flag & SI_DRAW_TILE) != 0;
}
void init_ss_to_texture_matrix(const ARegion *region,
const float UNUSED(image_resolution[2]),
float r_uv_to_texture[4][4]) const override
{
unit_m4(r_uv_to_texture);
float scale_x = 1.0 / BLI_rctf_size_x(&region->v2d.cur);
float scale_y = 1.0 / BLI_rctf_size_y(&region->v2d.cur);
float translate_x = scale_x * -region->v2d.cur.xmin;
float translate_y = scale_y * -region->v2d.cur.ymin;
r_uv_to_texture[0][0] = scale_x;
r_uv_to_texture[1][1] = scale_y;
r_uv_to_texture[3][0] = translate_x;
r_uv_to_texture[3][1] = translate_y;
}
};

View File

@ -54,20 +54,6 @@ class SpaceNodeAccessor : public AbstractSpaceAccessor {
BKE_image_release_ibuf(image, ibuf, lock);
}
bool has_view_override() const override
{
return true;
}
DRWView *create_view_override(const ARegion *region) override
{
/* Setup a screen pixel view. The backdrop of the node editor doesn't follow the region. */
float winmat[4][4], viewmat[4][4];
orthographic_m4(viewmat, 0.0, region->winx, 0.0, region->winy, 0.0, 1.0);
unit_m4(winmat);
return DRW_view_create(viewmat, winmat, nullptr, nullptr, nullptr);
}
void get_shader_parameters(ShaderParameters &r_shader_parameters,
ImBuf *ibuf,
bool UNUSED(is_tiled)) override
@ -120,18 +106,33 @@ class SpaceNodeAccessor : public AbstractSpaceAccessor {
*r_tex_tile_data = nullptr;
}
void get_image_mat(const ImBuf *image_buffer,
const ARegion *region,
float r_mat[4][4]) const override
bool use_tile_drawing() const override
{
unit_m4(r_mat);
const float ibuf_width = image_buffer->x;
const float ibuf_height = image_buffer->y;
return false;
}
r_mat[0][0] = ibuf_width * snode->zoom;
r_mat[1][1] = ibuf_height * snode->zoom;
r_mat[3][0] = (region->winx - snode->zoom * ibuf_width) / 2 + snode->xof;
r_mat[3][1] = (region->winy - snode->zoom * ibuf_height) / 2 + snode->yof;
/**
* The backdrop of the node editor isn't drawn in screen space UV space. But is locked with the
* screen.
*/
void init_ss_to_texture_matrix(const ARegion *region,
const float image_resolution[2],
float r_uv_to_texture[4][4]) const override
{
unit_m4(r_uv_to_texture);
float display_resolution[2];
mul_v2_v2fl(display_resolution, image_resolution, snode->zoom);
const float scale_x = display_resolution[0] / region->winx;
const float scale_y = display_resolution[1] / region->winy;
const float translate_x = ((region->winx - display_resolution[0]) * 0.5f + snode->xof) /
region->winx;
const float translate_y = ((region->winy - display_resolution[1]) * 0.5f + snode->yof) /
region->winy;
r_uv_to_texture[0][0] = scale_x;
r_uv_to_texture[1][1] = scale_y;
r_uv_to_texture[3][0] = translate_x;
r_uv_to_texture[3][1] = translate_y;
}
};

View File

@ -0,0 +1,76 @@
/*
* 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 2022, Blender Foundation.
*/
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BLI_rect.h"
#include "GPU_batch.h"
#include "GPU_texture.h"
struct TextureInfo {
/**
* \brief Is the texture clipped.
*
* Resources of clipped textures are freed and ignored when performing partial updates.
*/
bool visible : 1;
/**
* \brief does this texture need a full update.
*
* When set to false the texture can be updated using a partial update.
*/
bool dirty : 1;
/** \brief area of the texture in screen space. */
rctf clipping_bounds;
/** \brief uv area of the texture. */
rctf uv_bounds;
/**
* \brief Batch to draw the associated texton the screen.
*
* contans a VBO with `pos` and 'uv'.
* `pos` (2xF32) is relative to the origin of the space.
* `uv` (2xF32) reflect the uv bounds.
*/
GPUBatch *batch;
/**
* \brief GPU Texture for a partial region of the image editor.
*/
GPUTexture *texture;
~TextureInfo()
{
if (batch != nullptr) {
GPU_batch_discard(batch);
batch = nullptr;
}
if (texture != nullptr) {
GPU_texture_free(texture);
texture = nullptr;
}
}
};

View File

@ -0,0 +1,49 @@
/*
* 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 2022, Blender Foundation.
*/
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "DNA_image_types.h"
struct ImageTileWrapper {
ImageTile *image_tile;
ImageTileWrapper(ImageTile *image_tile) : image_tile(image_tile)
{
}
int get_tile_number() const
{
return image_tile->tile_number;
}
int get_tile_x_offset() const
{
int tile_number = get_tile_number();
return (tile_number - 1001) % 10;
}
int get_tile_y_offset() const
{
int tile_number = get_tile_number();
return (tile_number - 1001) / 10;
}
};

View File

@ -7,12 +7,7 @@
#define IMAGE_DRAW_FLAG_DEPTH (1 << 3)
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
#ifdef TILED_IMAGE
uniform sampler2DArray imageTileArray;
uniform sampler1DArray imageTileData;
#else
uniform sampler2D imageTexture;
#endif
uniform bool imgPremultiplied;
uniform int drawFlags;
@ -20,75 +15,49 @@ uniform vec2 farNearDistances;
uniform vec4 color;
uniform vec4 shuffle;
/* Maximum UV range.
* Negative UV coordinates and UV coordinates beyond maxUV would draw a border. */
uniform vec2 maxUv;
#define FAR_DISTANCE farNearDistances.x
#define NEAR_DISTANCE farNearDistances.y
in vec2 uvs;
#define Z_DEPTH_BORDER 1.0
#define Z_DEPTH_IMAGE 0.75
in vec2 uv_screen;
in vec2 uv_image;
out vec4 fragColor;
#ifdef TILED_IMAGE
/* TODO(fclem): deduplicate code. */
bool node_tex_tile_lookup(inout vec3 co, sampler2DArray ima, sampler1DArray map)
bool is_border(vec2 uv)
{
vec2 tile_pos = floor(co.xy);
if (tile_pos.x < 0 || tile_pos.y < 0 || tile_pos.x >= 10) {
return false;
}
float tile = 10.0 * tile_pos.y + tile_pos.x;
if (tile >= textureSize(map, 0).x) {
return false;
}
/* Fetch tile information. */
float tile_layer = texelFetch(map, ivec2(tile, 0), 0).x;
if (tile_layer < 0.0) {
return false;
}
vec4 tile_info = texelFetch(map, ivec2(tile, 1), 0);
co = vec3(((co.xy - tile_pos) * tile_info.zw) + tile_info.xy, tile_layer);
return true;
return (uv.x < 0.0 || uv.y < 0.0 || uv.x > maxUv.x || uv.y > maxUv.y);
}
#endif
void main()
{
vec4 tex_color;
/* Read texture */
#ifdef TILED_IMAGE
vec3 co = vec3(uvs, 0.0);
if (node_tex_tile_lookup(co, imageTileArray, imageTileData)) {
tex_color = texture(imageTileArray, co);
}
else {
tex_color = vec4(1.0, 0.0, 1.0, 1.0);
}
#else
vec2 uvs_clamped = ((drawFlags & IMAGE_DRAW_FLAG_DO_REPEAT) != 0) ?
fract(uvs) :
clamp(uvs, vec2(0.0), vec2(1.0));
tex_color = texture(imageTexture, uvs_clamped);
#endif
ivec2 uvs_clamped = ivec2(uv_screen);
vec4 tex_color = texelFetch(imageTexture, uvs_clamped, 0);
if ((drawFlags & IMAGE_DRAW_FLAG_APPLY_ALPHA) != 0) {
if (!imgPremultiplied) {
tex_color.rgb *= tex_color.a;
bool border = is_border(uv_image);
if (!border) {
if ((drawFlags & IMAGE_DRAW_FLAG_APPLY_ALPHA) != 0) {
if (!imgPremultiplied) {
tex_color.rgb *= tex_color.a;
}
}
if ((drawFlags & IMAGE_DRAW_FLAG_DEPTH) != 0) {
tex_color = smoothstep(FAR_DISTANCE, NEAR_DISTANCE, tex_color);
}
if ((drawFlags & IMAGE_DRAW_FLAG_SHUFFLING) != 0) {
tex_color = color * dot(tex_color, shuffle);
}
if ((drawFlags & IMAGE_DRAW_FLAG_SHOW_ALPHA) == 0) {
tex_color.a = 1.0;
}
}
if ((drawFlags & IMAGE_DRAW_FLAG_DEPTH) != 0) {
tex_color = smoothstep(FAR_DISTANCE, NEAR_DISTANCE, tex_color);
}
if ((drawFlags & IMAGE_DRAW_FLAG_SHUFFLING) != 0) {
tex_color = color * dot(tex_color, shuffle);
}
if ((drawFlags & IMAGE_DRAW_FLAG_SHOW_ALPHA) == 0) {
tex_color.a = 1.0;
}
fragColor = tex_color;
gl_FragDepth = border ? Z_DEPTH_BORDER : Z_DEPTH_IMAGE;
}

View File

@ -1,34 +1,24 @@
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#define IMAGE_DRAW_FLAG_DO_REPEAT (1 << 4)
#define IMAGE_DRAW_FLAG_USE_WORLD_POS (1 << 5)
#define IMAGE_Z_DEPTH 0.75
uniform int drawFlags;
in vec3 pos;
out vec2 uvs;
in vec2 pos;
in vec2 uv;
/* Normalized screen space uv coordinates. */
out vec2 uv_screen;
out vec2 uv_image;
void main()
{
/* `pos` contains the coordinates of a quad (-1..1). but we need the coordinates of an image
* plane (0..1) */
vec3 image_pos = pos * 0.5 + 0.5;
vec3 image_pos = vec3(pos, 0.0);
uv_screen = image_pos.xy;
uv_image = uv;
if ((drawFlags & IMAGE_DRAW_FLAG_DO_REPEAT) != 0) {
gl_Position = vec4(pos.xy, IMAGE_Z_DEPTH, 1.0);
uvs = point_view_to_object(image_pos).xy;
}
else {
vec3 world_pos = point_object_to_world(image_pos);
vec4 position = point_world_to_ndc(world_pos);
/* Move drawn pixels to the front. In the overlay engine the depth is used
* to detect if a transparency texture or the background color should be drawn.
* Vertices are between 0.0 and 0.2, Edges between 0.2 and 0.4
* actual pixels are at 0.75, 1.0 is used for the background. */
position.z = IMAGE_Z_DEPTH;
gl_Position = position;
/* UDIM texture uses the world position for tile selection. */
uvs = ((drawFlags & IMAGE_DRAW_FLAG_USE_WORLD_POS) != 0) ? world_pos.xy : image_pos.xy;
}
vec3 world_pos = point_object_to_world(image_pos);
vec4 position = point_world_to_ndc(world_pos);
gl_Position = position;
}