Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "image_texture_info.hh"
namespace blender::image_engine {
/** \brief Create gpu::Batch 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();
}
private:
void ensure_clear_batch()
{
GPU_BATCH_CLEAR_SAFE(info.batch);
if (info.batch == nullptr) {
info.batch = GPU_batch_calloc();
}
}
void init_batch()
{
gpu::VertBuf *vbo = create_vbo();
GPU_batch_init_ex(info.batch, GPU_PRIM_TRI_FAN, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
template<typename DataType, typename RectType>
static void fill_tri_fan_from_rect(DataType result[4][2], RectType &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;
}
gpu::VertBuf *create_vbo()
{
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, 4);
int pos[4][2];
fill_tri_fan_from_rect<int, rcti>(pos, info.clipping_bounds);
float uv[4][2];
fill_tri_fan_from_rect<float, rctf>(uv, info.clipping_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;
}
void ensure_format()
{
if (format.attr_len == 0) {
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SINT_32_32);
GPU_vertformat_attr_add(&format, "uv", gpu::VertAttrType::SFLOAT_32_32);
pos_id = GPU_vertformat_attr_id_get(&format, "pos");
uv_id = GPU_vertformat_attr_id_get(&format, "uv");
}
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BLI_vector.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
namespace blender::image_engine {
struct FloatImageBuffer {
ImBuf *source_buffer = nullptr;
ImBuf *float_buffer = nullptr;
bool is_used = true;
FloatImageBuffer(ImBuf *source_buffer, ImBuf *float_buffer)
: source_buffer(source_buffer), float_buffer(float_buffer)
{
}
FloatImageBuffer(FloatImageBuffer &&other) noexcept
{
source_buffer = other.source_buffer;
float_buffer = other.float_buffer;
is_used = other.is_used;
other.source_buffer = nullptr;
other.float_buffer = nullptr;
}
virtual ~FloatImageBuffer()
{
IMB_freeImBuf(float_buffer);
float_buffer = nullptr;
source_buffer = nullptr;
}
FloatImageBuffer &operator=(FloatImageBuffer &&other) noexcept
{
this->source_buffer = other.source_buffer;
this->float_buffer = other.float_buffer;
is_used = other.is_used;
other.source_buffer = nullptr;
other.float_buffer = nullptr;
return *this;
}
};
/**
* \brief Float buffer cache for image buffers.
*
* Image buffers might not have float buffers which are required for the image engine.
* Image buffers are not allowed to have both a float buffer and a byte buffer as some
* functionality doesn't know what to do.
*
* For this reason we store the float buffer in separate image buffers. The FloatBufferCache keep
* track of the cached buffers and if they are still used.
*/
struct FloatBufferCache {
private:
Vector<FloatImageBuffer> cache_;
public:
ImBuf *cached_float_buffer(ImBuf *image_buffer)
{
/* Check if we can use the float buffer of the given image_buffer. */
if (image_buffer->float_data() != nullptr) {
BLI_assert_msg(
IMB_colormanagement_space_name_is_scene_linear(
IMB_colormanagement_get_float_colorspace(image_buffer)) ||
IMB_colormanagement_space_name_is_data(
IMB_colormanagement_get_float_colorspace(image_buffer)),
"Expected float buffer to be scene_linear or data - if there are code paths where this "
"isn't the case we should convert those and add to the FloatBufferCache as well.");
return image_buffer;
}
/* Do we have a cached float buffer. */
for (FloatImageBuffer &item : cache_) {
if (item.source_buffer == image_buffer) {
item.is_used = true;
return item.float_buffer;
}
}
/* Generate a new float buffer. */
IMB_float_from_byte(image_buffer);
ImBuf *new_imbuf = IMB_allocImBuf(image_buffer->x, image_buffer->y, ImBufFlags::Zero);
new_imbuf->color_mode = image_buffer->color_mode;
new_imbuf->float_buffer = image_buffer->float_buffer;
image_buffer->float_buffer = {};
cache_.append(FloatImageBuffer(image_buffer, new_imbuf));
return new_imbuf;
}
void reset_usage_flags()
{
for (FloatImageBuffer &buffer : cache_) {
buffer.is_used = false;
}
}
void mark_used(const ImBuf *image_buffer)
{
for (FloatImageBuffer &item : cache_) {
if (item.source_buffer == image_buffer) {
item.is_used = true;
return;
}
}
}
void remove_unused_buffers()
{
for (int64_t i = cache_.size() - 1; i >= 0; i--) {
if (!cache_[i].is_used) {
cache_.remove_and_reorder(i);
}
}
}
void clear()
{
cache_.clear();
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2026 Blender Foundation.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#include "image_drawing_mode_image_space.hh"
#include "image_instance.hh"
#include "image_shader.hh"
namespace blender::image_engine {
ImageSpaceDrawingMode::ImageSpaceDrawingMode(Instance &instance,
gpu::Texture *texture,
gpu::Texture *tile_mapping_texture)
: instance_(instance), texture_(texture), tile_mapping_texture_(tile_mapping_texture)
{
GPU_texture_ref(texture_);
if (tile_mapping_texture_) {
GPU_texture_ref(tile_mapping_texture_);
}
}
ImageSpaceDrawingMode::~ImageSpaceDrawingMode()
{
GPU_texture_free(texture_);
if (tile_mapping_texture_) {
GPU_texture_free(tile_mapping_texture_);
}
}
void ImageSpaceDrawingMode::begin_sync() const {}
void ImageSpaceDrawingMode::image_sync(blender::Image * /*image*/, ImageUser * /*iuser*/) const {}
void ImageSpaceDrawingMode::draw_viewport() const
{
PassSimple &pass = instance_.state.image_ps;
pass.init();
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS);
pass.shader_set(tile_mapping_texture_ ? ShaderModule::module_get().image_tiled.get() :
ShaderModule::module_get().image.get());
pass.push_constant("image_matrix", float4x4(instance_.state.ss_to_texture));
pass.push_constant("far_near_distances", instance_.state.sh_params.far_near);
pass.push_constant("shuffle", instance_.state.sh_params.shuffle);
pass.push_constant("draw_flags", int32_t(instance_.state.sh_params.flags));
pass.push_constant("is_image_premultiplied", instance_.state.sh_params.use_premul_alpha);
/* The shader will discard fragments that are outside of the image if repeating is disabled, so
* we just always have repeat mode enabled. */
const GPUSamplerState sampler = {.filtering = GPU_SAMPLER_FILTERING_DEFAULT,
.extend_x = GPU_SAMPLER_EXTEND_MODE_REPEAT,
.extend_yz = GPU_SAMPLER_EXTEND_MODE_REPEAT};
if (tile_mapping_texture_) {
pass.bind_texture("image_tile_array", texture_, sampler);
pass.bind_texture("image_tile_data", tile_mapping_texture_, sampler);
}
else {
pass.push_constant("is_repeated", instance_.state.flags.do_tile_drawing);
pass.bind_texture("image_tx", texture_, sampler);
}
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
instance_.manager->submit(instance_.state.image_ps, instance_.state.view);
}
}; // namespace blender::image_engine

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2026 Blender Foundation.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "image_private.hh"
namespace blender::image_engine {
class Instance;
/**
* Drawing mode optimized for textures that fits within the GPU specifications.
*
* Each GPU has a max texture size. Textures larger than this size aren't able to be allocated on
* the GPU. For large textures use #ScreenSpaceDrawingMode.
*/
class ImageSpaceDrawingMode : public AbstractDrawingMode {
private:
Instance &instance_;
gpu::Texture *texture_;
gpu::Texture *tile_mapping_texture_ = nullptr;
public:
ImageSpaceDrawingMode(Instance &instance,
gpu::Texture *texture,
gpu::Texture *tile_mapping_texture = nullptr);
~ImageSpaceDrawingMode() override;
void begin_sync() const override;
void image_sync(blender::Image *image, blender::ImageUser *iuser) const override;
void draw_viewport() const override;
};
}; // namespace blender::image_engine

View File

@@ -0,0 +1,390 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "draw_view_data.hh"
#include "image_drawing_mode_screen_space.hh"
#include "image_instance.hh"
#include "image_shader.hh"
#include "BKE_image.hh"
#include "BKE_image_partial_update.hh"
namespace blender::image_engine {
void ScreenSpaceDrawingMode::add_shgroups() const
{
PassSimple &pass = instance_.state.image_ps;
gpu::Shader *shader = ShaderModule::module_get().color.get();
const ShaderParameters &sh_params = instance_.state.sh_params;
DefaultTextureList *dtxl = DRW_context_get()->viewport_texture_list_get();
pass.shader_set(shader);
pass.push_constant("far_near_distances", sh_params.far_near);
pass.push_constant("shuffle", sh_params.shuffle);
pass.push_constant("draw_flags", int32_t(sh_params.flags));
pass.push_constant("is_image_premultiplied", sh_params.use_premul_alpha);
pass.bind_texture("depth_tx", dtxl->depth);
float4x4 image_mat = float4x4::identity();
ResourceHandleRange handle = instance_.manager->resource_handle(image_mat);
for (const TextureInfo &info : instance_.state.texture_infos) {
PassSimple::Sub &sub = pass.sub("Texture");
sub.push_constant("offset", info.offset());
sub.bind_texture("image_tx", info.texture);
sub.draw(info.batch, handle);
}
}
void ScreenSpaceDrawingMode::add_depth_shgroups(blender::Image *image, ImageUser *image_user) const
{
PassSimple &pass = instance_.state.depth_ps;
gpu::Shader *shader = ShaderModule::module_get().depth.get();
pass.shader_set(shader);
float4x4 image_mat = float4x4::identity();
ResourceHandleRange handle = instance_.manager->resource_handle(image_mat);
ImageUser tile_user = {nullptr};
if (image_user) {
tile_user = *image_user;
}
for (const TextureInfo &info : instance_.state.texture_infos) {
for (ImageTile &image_tile_ptr : image->tiles) {
const ImageTileWrapper image_tile(&image_tile_ptr);
const int tile_x = image_tile.get_tile_x_offset();
const int tile_y = image_tile.get_tile_y_offset();
tile_user.tile = image_tile.get_tile_number();
/* NOTE: `BKE_image_has_ibuf` doesn't work as it fails for render results. That could be a
* bug or a feature. For now we just acquire to determine if there is a texture. */
void *lock;
ImBuf *tile_buffer = BKE_image_acquire_ibuf(image, &tile_user, &lock);
if (tile_buffer != nullptr) {
instance_.state.float_buffers.mark_used(tile_buffer);
PassSimple::Sub &sub = pass.sub("Tile");
float4 min_max_uv(tile_x, tile_y, tile_x + 1, tile_y + 1);
sub.push_constant("min_max_uv", min_max_uv);
sub.draw(info.batch, handle);
}
BKE_image_release_ibuf(image, tile_buffer, lock);
}
}
}
void ScreenSpaceDrawingMode::update_textures(blender::Image *image, ImageUser *image_user) const
{
State &state = instance_.state;
PartialUpdateChecker<ImageTileData> checker(image, image_user, state.partial_update.user);
PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes();
switch (changes.get_result_code()) {
case ePartialUpdateCollectResult::FullUpdateNeeded:
state.mark_all_texture_slots_dirty();
state.float_buffers.clear();
break;
case ePartialUpdateCollectResult::NoChangesDetected:
break;
case ePartialUpdateCollectResult::PartialChangesDetected:
/* Partial update when wrap repeat is enabled is not supported. */
if (state.flags.do_tile_drawing) {
state.float_buffers.clear();
state.mark_all_texture_slots_dirty();
}
else {
do_partial_update(changes);
}
break;
}
do_full_update_for_dirty_textures(image_user);
}
void ScreenSpaceDrawingMode::do_partial_update_float_buffer(
ImBuf *float_buffer, PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const
{
ImBuf *src = iterator.tile_data.tile_buffer;
BLI_assert(float_buffer->float_data() != nullptr);
BLI_assert(float_buffer->byte_data() == nullptr);
BLI_assert(src->float_data() == nullptr);
BLI_assert(src->byte_data() != nullptr);
/* Calculate the overlap between the updated region and the buffer size. Partial Update Checker
* always returns a tile (256x256). Which could lay partially outside the buffer when using
* different resolutions.
*/
rcti buffer_rect;
BLI_rcti_init(&buffer_rect, 0, float_buffer->x, 0, float_buffer->y);
rcti clipped_update_region;
const bool has_overlap = BLI_rcti_isect(
&buffer_rect, &iterator.changed_region.region, &clipped_update_region);
if (!has_overlap) {
return;
}
IMB_float_from_byte_ex(float_buffer, src, &clipped_update_region);
}
void ScreenSpaceDrawingMode::do_partial_update(
PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const
{
while (iterator.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
/* Quick exit when tile_buffer isn't available. */
if (iterator.tile_data.tile_buffer == nullptr) {
continue;
}
ImBuf *tile_buffer = instance_.state.float_buffers.cached_float_buffer(
iterator.tile_data.tile_buffer);
if (tile_buffer != iterator.tile_data.tile_buffer) {
do_partial_update_float_buffer(tile_buffer, iterator);
}
const float tile_width = float(iterator.tile_data.tile_buffer->x);
const float tile_height = float(iterator.tile_data.tile_buffer->y);
for (const TextureInfo &info : instance_.state.texture_infos) {
/* Dirty images will receive a full update. No need to do a partial one now. */
if (info.need_full_update) {
continue;
}
gpu::Texture *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 = float(tile_accessor.get_tile_x_offset());
float tile_offset_y = 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,
float(changed_region_in_texel_space->xmin) / float(iterator.tile_data.tile_buffer->x) +
tile_offset_x,
float(changed_region_in_texel_space->xmax) / float(iterator.tile_data.tile_buffer->x) +
tile_offset_x,
float(changed_region_in_texel_space->ymin) / float(iterator.tile_data.tile_buffer->y) +
tile_offset_y,
float(changed_region_in_texel_space->ymax) / 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.clipping_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.clipping_uv_bounds.xmin) *
texture_width / BLI_rctf_size_x(&info.clipping_uv_bounds)),
floor((changed_overlapping_region_in_uv_space.xmax - info.clipping_uv_bounds.xmin) *
texture_width / BLI_rctf_size_x(&info.clipping_uv_bounds)),
ceil((changed_overlapping_region_in_uv_space.ymin - info.clipping_uv_bounds.ymin) *
texture_height / BLI_rctf_size_y(&info.clipping_uv_bounds)),
ceil((changed_overlapping_region_in_uv_space.ymax - info.clipping_uv_bounds.ymin) *
texture_height / BLI_rctf_size_y(&info.clipping_uv_bounds)));
gpu_texture_region_to_update.xmax = min_ii(gpu_texture_region_to_update.xmax,
info.clipping_bounds.xmax);
gpu_texture_region_to_update.ymax = min_ii(gpu_texture_region_to_update.ymax,
info.clipping_bounds.ymax);
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, ImBufFlags::FloatData);
int offset = 0;
float *float_data = extracted_buffer.float_data_for_write();
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.clipping_uv_bounds.ymax * yf + info.clipping_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.clipping_uv_bounds.xmax * xf + info.clipping_uv_bounds.xmin * (1.0 - xf) -
tile_offset_x;
imbuf::interpolate_nearest_border_fl(
tile_buffer, &float_data[offset * 4], u * tile_buffer->x, v * tile_buffer->y);
offset++;
}
}
IMB_gpu_clamp_half_float(&extracted_buffer);
GPU_texture_update_sub(texture,
GPU_DATA_FLOAT,
float_data,
gpu_texture_region_to_update.xmin,
gpu_texture_region_to_update.ymin,
0,
extracted_buffer.x,
extracted_buffer.y,
0);
IMB_free_all_data(&extracted_buffer);
}
}
}
void ScreenSpaceDrawingMode::do_full_update_for_dirty_textures(const ImageUser *image_user) const
{
for (TextureInfo &info : instance_.state.texture_infos) {
if (!info.need_full_update) {
continue;
}
do_full_update_gpu_texture(info, image_user);
}
}
void ScreenSpaceDrawingMode::do_full_update_gpu_texture(TextureInfo &info,
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, ImBufFlags::FloatData);
ImageUser tile_user = {nullptr};
if (image_user) {
tile_user = *image_user;
}
void *lock;
blender::Image *image = instance_.state.image;
for (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) {
do_full_update_texture_slot(info, texture_buffer, *tile_buffer, image_tile);
}
BKE_image_release_ibuf(image, tile_buffer, lock);
}
IMB_gpu_clamp_half_float(&texture_buffer);
GPU_texture_update(info.texture, GPU_DATA_FLOAT, texture_buffer.float_data());
IMB_free_all_data(&texture_buffer);
}
void ScreenSpaceDrawingMode::do_full_update_texture_slot(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;
ImBuf *float_tile_buffer = instance_.state.float_buffers.cached_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. */
float3x3 uv_to_texel;
rctf texture_area;
rctf tile_area;
BLI_rctf_init(&texture_area, 0.0, texture_width, 0.0, texture_height);
BLI_rctf_init(
&tile_area,
tile_buffer.x * (texture_info.clipping_uv_bounds.xmin - image_tile.get_tile_x_offset()),
tile_buffer.x * (texture_info.clipping_uv_bounds.xmax - image_tile.get_tile_x_offset()),
tile_buffer.y * (texture_info.clipping_uv_bounds.ymin - image_tile.get_tile_y_offset()),
tile_buffer.y * (texture_info.clipping_uv_bounds.ymax - image_tile.get_tile_y_offset()));
BLI_rctf_transform_calc_m3_pivot_min(&tile_area, &texture_area, uv_to_texel.ptr());
uv_to_texel = math::invert(uv_to_texel);
rctf crop_rect;
const rctf *crop_rect_ptr = nullptr;
eIMBTransformMode transform_mode;
if (instance_.state.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(float_tile_buffer,
&texture_buffer,
transform_mode,
IMB_FILTER_NEAREST,
uv_to_texel,
crop_rect_ptr);
}
void ScreenSpaceDrawingMode::begin_sync() const
{
{
DefaultTextureList *dtxl = DRW_context_get()->viewport_texture_list_get();
instance_.state.depth_fb.ensure(GPU_ATTACHMENT_TEXTURE(dtxl->depth));
instance_.state.color_fb.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(dtxl->color));
}
{
PassSimple &pass = instance_.state.image_ps;
pass.init();
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL);
}
{
PassSimple &pass = instance_.state.depth_ps;
pass.init();
pass.state_set(DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL);
}
}
void ScreenSpaceDrawingMode::image_sync(blender::Image *image, ImageUser *iuser) const
{
State &state = instance_.state;
state.partial_update.ensure_image(image);
state.clear_need_full_update_flag();
/* Step: Find out which screen space textures are needed to draw on the screen. Recycle
* textures that are not on screen anymore. */
OneTexture method(&state);
method.ensure_texture_infos();
method.update_bounds(instance_.region);
/* Step: Check for changes in the image user compared to the last time. */
state.update_image_usage(iuser);
/* Step: Update the GPU textures based on the changes in the image. */
method.ensure_gpu_textures_allocation();
update_textures(image, iuser);
/* Step: Add the GPU textures to the shgroup. */
state.update_batches();
if (!state.flags.do_tile_drawing) {
add_depth_shgroups(image, iuser);
}
add_shgroups();
}
void ScreenSpaceDrawingMode::draw_viewport() const
{
float clear_depth = instance_.state.flags.do_tile_drawing ? 0.75 : 1.0f;
GPU_framebuffer_bind(instance_.state.depth_fb);
instance_.state.depth_fb.clear_depth(clear_depth);
instance_.manager->submit(instance_.state.depth_ps, instance_.state.view);
GPU_framebuffer_bind(instance_.state.color_fb);
GPU_framebuffer_clear_color(instance_.state.color_fb, double4(0.0));
instance_.manager->submit(instance_.state.image_ps, instance_.state.view);
}
} // namespace blender::image_engine

View File

@@ -0,0 +1,316 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "image_batches.hh"
#include "image_private.hh"
#include "DNA_windowmanager_types.h"
namespace blender::image_engine {
class Instance;
constexpr float EPSILON_UV_BOUNDS = 0.00001f;
class BaseTextureMethod {
protected:
State *instance_data;
protected:
BaseTextureMethod(State *instance_data) : instance_data(instance_data) {}
public:
/**
* \brief Ensure enough texture infos are allocated in `instance_data`.
*/
virtual void ensure_texture_infos() = 0;
/**
* \brief Update the uv and region bounds of all texture_infos of instance_data.
*/
virtual void update_bounds(const ARegion *region) = 0;
virtual void ensure_gpu_textures_allocation() = 0;
};
/**
* Uses a single texture that covers the area. Every zoom/pan change requires a full
* update of the texture.
*/
class OneTexture : public BaseTextureMethod {
public:
OneTexture(State *instance_data) : BaseTextureMethod(instance_data) {}
void ensure_texture_infos() override
{
instance_data->texture_infos.resize(1);
}
void update_bounds(const ARegion *region) override
{
float3x3 mat = instance_data->ss_to_texture;
float2 region_uv_min = math::transform_point(mat, float2(0.0f, 0.0f));
float2 region_uv_max = math::transform_point(mat, float2(1.0f, 1.0f));
TextureInfo &texture_info = instance_data->texture_infos[0];
texture_info.tile_id = int2(0);
texture_info.need_full_update = false;
rctf new_clipping_uv_bounds;
BLI_rctf_init(&new_clipping_uv_bounds,
region_uv_min.x,
region_uv_max.x,
region_uv_min.y,
region_uv_max.y);
if (memcmp(&new_clipping_uv_bounds, &texture_info.clipping_uv_bounds, sizeof(rctf))) {
texture_info.clipping_uv_bounds = new_clipping_uv_bounds;
texture_info.need_full_update = true;
}
rcti new_clipping_bounds;
BLI_rcti_init(&new_clipping_bounds, 0, region->winx, 0, region->winy);
if (memcmp(&new_clipping_bounds, &texture_info.clipping_bounds, sizeof(rcti))) {
texture_info.clipping_bounds = new_clipping_bounds;
texture_info.need_full_update = true;
}
}
void ensure_gpu_textures_allocation() override
{
TextureInfo &texture_info = instance_data->texture_infos[0];
int2 texture_size = int2(BLI_rcti_size_x(&texture_info.clipping_bounds),
BLI_rcti_size_y(&texture_info.clipping_bounds));
texture_info.ensure_gpu_texture(texture_size);
}
};
/**
* \brief Screen space method using a multiple textures covering the region.
*
* This method improves panning speed, but has some drawing artifacts and
* therefore isn't selected.
*/
template<size_t Divisions> class ScreenTileTextures : public BaseTextureMethod {
public:
static const size_t TexturesPerDimension = Divisions + 1;
static const size_t TexturesRequired = TexturesPerDimension * TexturesPerDimension;
static const size_t VerticesPerDimension = TexturesPerDimension + 1;
private:
/**
* \brief Helper struct to pair a texture info and a region in uv space of the area.
*/
struct TextureInfoBounds {
TextureInfo *info = nullptr;
rctf uv_bounds;
/* Offset of this tile to be drawn on the screen (number of tiles from bottom left corner). */
int2 tile_id;
};
public:
ScreenTileTextures(State *instance_data) : BaseTextureMethod(instance_data) {}
/**
* \brief Ensure enough texture infos are allocated in `instance_data`.
*/
void ensure_texture_infos() override
{
instance_data->texture_infos.resize(TexturesRequired);
}
/**
* \brief Update the uv and region bounds of all texture_infos of instance_data.
*/
void update_bounds(const ARegion *region) override
{
/* determine uv_area of the region. */
Vector<TextureInfo *> unassigned_textures;
float3x3 mat = instance_data->ss_to_texture;
float2 region_uv_min = math::transform_point(mat, float2(0.0f, 0.0f));
float2 region_uv_max = math::transform_point(mat, float2(1.0f, 1.0f));
float2 region_uv_span = region_uv_max - region_uv_min;
/* Calculate uv coordinates of each vert in the grid of textures. */
/* Construct the uv bounds of the 4 textures that are needed to fill the region. */
Vector<TextureInfoBounds> info_bounds = create_uv_bounds(region_uv_span, region_uv_min);
assign_texture_infos_by_uv_bounds(info_bounds, unassigned_textures);
assign_unused_texture_infos(info_bounds, unassigned_textures);
/* Calculate the region bounds from the uv bounds. */
rctf region_uv_bounds;
BLI_rctf_init(
&region_uv_bounds, region_uv_min.x, region_uv_max.x, region_uv_min.y, region_uv_max.y);
update_region_bounds_from_uv_bounds(region_uv_bounds, int2(region->winx, region->winy));
}
/**
* Get the texture size of a single texture for the current settings.
*/
int2 gpu_texture_size() const
{
float2 viewport_size = DRW_context_get()->viewport_size_get();
int2 texture_size(ceil(viewport_size.x / Divisions), ceil(viewport_size.y / Divisions));
return texture_size;
}
void ensure_gpu_textures_allocation() override
{
int2 texture_size = gpu_texture_size();
for (TextureInfo &info : instance_data->texture_infos) {
info.ensure_gpu_texture(texture_size);
}
}
private:
Vector<TextureInfoBounds> create_uv_bounds(float2 region_uv_span, float2 region_uv_min)
{
float2 uv_coords[VerticesPerDimension][VerticesPerDimension];
float2 region_tile_uv_span = region_uv_span / float2(float(Divisions));
float2 onscreen_multiple = (math::floor(region_uv_min / region_tile_uv_span) + float2(1.0f)) *
region_tile_uv_span;
for (int y = 0; y < VerticesPerDimension; y++) {
for (int x = 0; x < VerticesPerDimension; x++) {
uv_coords[x][y] = region_tile_uv_span * float2(float(x - 1), float(y - 1)) +
onscreen_multiple;
}
}
Vector<TextureInfoBounds> info_bounds;
for (int x = 0; x < TexturesPerDimension; x++) {
for (int y = 0; y < TexturesPerDimension; y++) {
TextureInfoBounds texture_info_bounds;
texture_info_bounds.tile_id = int2(x, y);
BLI_rctf_init(&texture_info_bounds.uv_bounds,
uv_coords[x][y].x,
uv_coords[x + 1][y + 1].x,
uv_coords[x][y].y,
uv_coords[x + 1][y + 1].y);
info_bounds.append(texture_info_bounds);
}
}
return info_bounds;
}
void assign_texture_infos_by_uv_bounds(Vector<TextureInfoBounds> &info_bounds,
Vector<TextureInfo *> &r_unassigned_textures)
{
for (TextureInfo &info : instance_data->texture_infos) {
bool assigned = false;
for (TextureInfoBounds &info_bound : info_bounds) {
if (info_bound.info == nullptr &&
BLI_rctf_compare(&info_bound.uv_bounds, &info.clipping_uv_bounds, 0.001))
{
info_bound.info = &info;
info.tile_id = info_bound.tile_id;
assigned = true;
break;
}
}
if (!assigned) {
r_unassigned_textures.append(&info);
}
}
}
void assign_unused_texture_infos(Vector<TextureInfoBounds> &info_bounds,
Vector<TextureInfo *> &unassigned_textures)
{
for (TextureInfoBounds &info_bound : info_bounds) {
if (info_bound.info == nullptr) {
info_bound.info = unassigned_textures.pop_last();
info_bound.info->tile_id = info_bound.tile_id;
info_bound.info->need_full_update = true;
info_bound.info->clipping_uv_bounds = info_bound.uv_bounds;
}
}
}
void update_region_bounds_from_uv_bounds(const rctf &region_uv_bounds, const int2 region_size)
{
rctf region_bounds;
BLI_rctf_init(&region_bounds, 0.0, region_size.x, 0.0, region_size.y);
float4x4 uv_to_screen;
BLI_rctf_transform_calc_m4_pivot_min(&region_uv_bounds, &region_bounds, uv_to_screen.ptr());
int2 tile_origin(0);
for (const TextureInfo &info : instance_data->texture_infos) {
if (info.tile_id == int2(0)) {
tile_origin = int2(math::transform_point(
uv_to_screen,
float3(info.clipping_uv_bounds.xmin, info.clipping_uv_bounds.ymin, 0.0)));
break;
}
}
const int2 texture_size = gpu_texture_size();
for (TextureInfo &info : instance_data->texture_infos) {
int2 bottom_left = tile_origin + texture_size * info.tile_id;
int2 top_right = bottom_left + texture_size;
BLI_rcti_init(&info.clipping_bounds, bottom_left.x, top_right.x, bottom_left.y, top_right.y);
}
}
};
using namespace blender::bke::image::partial_update;
using namespace blender::bke::image;
class ScreenSpaceDrawingMode : public AbstractDrawingMode {
private:
Instance &instance_;
public:
ScreenSpaceDrawingMode(Instance &instance) : instance_(instance) {}
private:
void add_shgroups() const;
/**
* \brief add depth drawing calls.
*
* The depth is used to identify if the tile exist or transparent.
*/
void add_depth_shgroups(blender::Image *image, blender::ImageUser *image_user) const;
/**
* \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(blender::Image *image, blender::ImageUser *image_user) const;
/**
* Update the float buffer in the region given by the partial update checker.
*/
void do_partial_update_float_buffer(
ImBuf *float_buffer, PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const;
void do_partial_update(PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const;
void do_full_update_for_dirty_textures(const blender::ImageUser *image_user) const;
void do_full_update_gpu_texture(TextureInfo &info, const blender::ImageUser *image_user) const;
/**
* texture_buffer is the image buffer belonging to the texture_info.
* tile_buffer is the image buffer of the tile.
*/
void do_full_update_texture_slot(const TextureInfo &texture_info,
ImBuf &texture_buffer,
ImBuf &tile_buffer,
const ImageTileWrapper &image_tile) const;
public:
void begin_sync() const override;
void image_sync(blender::Image *image, blender::ImageUser *iuser) const override;
void draw_viewport() const override;
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*
* Draw engine to draw the Image/UV editor
*/
#include "image_engine.h"
#include "image_instance.hh"
#include "image_shader.hh"
namespace blender::image_engine {
DrawEngine *Engine::create_instance()
{
return new Instance();
}
void Engine::free_static()
{
ShaderModule::module_free();
}
} // namespace blender::image_engine

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "DRW_render.hh"
namespace blender::image_engine {
struct Engine : public DrawEngine::Pointer {
DrawEngine *create_instance() final;
static void free_static();
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,273 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <DRW_render.hh>
#include "BKE_context.hh"
#include "GPU_capabilities.hh"
#include "DRW_engine.hh"
#include "draw_view_data.hh"
#include "image_drawing_mode_image_space.hh"
#include "image_drawing_mode_screen_space.hh"
#include "image_private.hh"
#include "image_space.hh"
#include "image_space_image.hh"
#include "image_space_node.hh"
#include "BLI_math_matrix.hh"
#include "DNA_space_types.h"
namespace blender::image_engine {
static inline std::unique_ptr<AbstractSpaceAccessor> space_accessor_from_space(
SpaceLink *space_link)
{
if (space_link->spacetype == SPACE_IMAGE) {
return std::make_unique<SpaceImageAccessor>(
static_cast<SpaceImage *>(static_cast<void *>(space_link)));
}
if (space_link->spacetype == SPACE_NODE) {
return std::make_unique<SpaceNodeAccessor>(
static_cast<SpaceNode *>(static_cast<void *>(space_link)));
}
BLI_assert_unreachable();
return nullptr;
}
class Instance : public DrawEngine {
private:
std::unique_ptr<AbstractSpaceAccessor> space_;
std::unique_ptr<AbstractDrawingMode> drawing_mode_;
Main *main_;
public:
const ARegion *region;
State state;
Manager *manager = nullptr;
public:
StringRefNull name_get() final
{
return "UV/Image";
}
void init() final
{
const DRWContext *ctx_state = DRW_context_get();
main_ = CTX_data_main(ctx_state->evil_C);
region = ctx_state->region;
space_ = space_accessor_from_space(ctx_state->space_data);
manager = DRW_manager_get();
}
/* Constructs either a screen space or an image space drawing mode depending on if the image can
* fit in a GPU texture. So we just need to retrieve the image buffer and check if its size is
* safe for GPU use. */
std::unique_ptr<AbstractDrawingMode> get_drawing_mode()
{
if (this->state.image->source != IMA_SRC_TILED) {
void *lock;
ImBuf *buffer = BKE_image_acquire_ibuf_gpu(
this->state.image, space_->get_image_user(), &lock);
BLI_SCOPED_DEFER([&]() { BKE_image_release_ibuf(this->state.image, buffer, lock); });
/* The image buffer already has a GPU texture, so use image space drawing. */
if (buffer && buffer->gpu.texture) {
return std::make_unique<ImageSpaceDrawingMode>(*this, buffer->gpu.texture);
}
/* Buffer does not exist or image will not fit in a GPU texture, use screen space drawing. */
if (!buffer || (!buffer->float_data() && !buffer->byte_data()) ||
!GPU_is_safe_texture_size(buffer->x, buffer->y))
{
return std::make_unique<ScreenSpaceDrawingMode>(*this);
}
/* GPU drawing will limit image resolution due to the GPU back-end having a lower maximum
* texture size or a resolution limit in the preferences, so use screen space drawing to get
* the full resolution. */
if (GPU_texture_size_with_limit(buffer->x) != buffer->x ||
GPU_texture_size_with_limit(buffer->y) != buffer->y)
{
return std::make_unique<ScreenSpaceDrawingMode>(*this);
}
/* Image can fit in a GPU texture, use image space drawing. */
BKE_image_ensure_gpu_texture(this->state.image, space_->get_image_user());
gpu::Texture *texture = BKE_image_get_gpu_viewer_texture(
this->state.image, space_->get_image_user(), buffer);
return std::make_unique<ImageSpaceDrawingMode>(*this, texture);
}
for (ImageTile &tile : this->state.image->tiles) {
ImageTileWrapper image_tile(&tile);
ImageUser tile_user = space_->get_image_user() ? *space_->get_image_user() :
ImageUser{.scene = nullptr};
tile_user.tile = image_tile.get_tile_number();
ImBuf *buffer = BKE_image_acquire_ibuf_gpu(this->state.image, &tile_user, nullptr);
BLI_SCOPED_DEFER([&]() { BKE_image_release_ibuf(this->state.image, buffer, nullptr); });
if (!buffer) {
continue;
}
/* Image will not fit in a GPU texture, use screen space drawing. */
if (!GPU_is_safe_texture_size(buffer->x, buffer->y)) {
return std::make_unique<ScreenSpaceDrawingMode>(*this);
}
/* GPU drawing will limit image resolution due to the GPU back-end having a lower maximum
* texture size or a resolution limit in the preferences, so use screen space drawing to get
* the full resolution. */
if (GPU_texture_size_with_limit(buffer->x) != buffer->x ||
GPU_texture_size_with_limit(buffer->y) != buffer->y)
{
return std::make_unique<ScreenSpaceDrawingMode>(*this);
}
}
/* Image can fit in a GPU texture, use image space drawing. */
BKE_image_ensure_gpu_texture(this->state.image, space_->get_image_user());
ImageGPUTextures gpu_tiles_textures = BKE_image_get_gpu_material_texture(
this->state.image, space_->get_image_user(), true);
return std::make_unique<ImageSpaceDrawingMode>(
*this, *gpu_tiles_textures.texture, *gpu_tiles_textures.tile_mapping);
}
void begin_sync() final
{
/* Setup full screen view matrix. */
float4x4 viewmat = math::projection::orthographic(
0.0f, float(region->winx), 0.0f, float(region->winy), 0.0f, 1.0f);
float4x4 winmat = float4x4::identity();
state.view.sync(viewmat, winmat);
state.flags.do_tile_drawing = false;
this->image_sync();
drawing_mode_.reset();
this->state.float_buffers.reset_usage_flags();
if (this->state.image) {
this->drawing_mode_ = this->get_drawing_mode();
drawing_mode_->begin_sync();
drawing_mode_->image_sync(state.image, space_->get_image_user());
}
}
/* Computes a transformation matrix from the normalized screen space coordinates with half pixel
* offsets into the image sampler space. */
float3x3 compute_screen_space_to_sampler_space_transformation(const float2 output_size,
const float2 image_offset,
const float2 image_size,
const float2 pan_offset,
const float zoom,
const float aspect_ratio) const
{
/* Transforms output normalized screen coordinates with half pixel offsets into integer data
* coordinates. */
const float3x3 output_screen_uv_to_output_texel = math::from_scale<float3x3, 2>(output_size);
const float3x3 output_texel_to_output_data = math::from_location<float3x3>(float2(-0.5f));
/* Transforms output data coordinates into the centered virtual space. */
const float2 output_center = float2(output_size) / 2.0f;
const float2 output_translation = -output_center;
const float3x3 output_data_to_virtual = math::from_location<float3x3>(output_translation);
/* Transform the image in pixel space based on the pan offset, zoom, and aspect ratio. */
const float3x3 image_transformation = math::from_loc_scale<float3x3>(
pan_offset, float2(zoom) * float2(1.0f, aspect_ratio));
/* Transforms image data coordinates into the centered virtual space with the image offset. We
* also bias translations to avoids the round-to-even behavior of some GPUs at pixel
* boundaries. */
const float2 image_center = image_size / 2.0f;
const float2 corrective_translation = float2(std::numeric_limits<float>::epsilon() * 10e3f);
const float2 image_translation = image_offset - image_center + corrective_translation;
const float3x3 image_data_to_virtual = math::translate(image_transformation,
image_translation);
/* Transforms the output data space to the image data space. */
const float3x3 virtual_to_image_data = math::invert(image_data_to_virtual);
const float3x3 output_data_to_image_data = virtual_to_image_data * output_data_to_virtual;
/* Transform from image data coordinates to image sampler normalized coordinates with half
* pixel offsets. */
const float3x3 image_data_to_image_texel = math::from_location<float3x3>(float2(0.5f));
const float3x3 image_texel_to_image_sampler = math::from_scale<float3x3, 2>(1.0f / image_size);
const float3x3 output_screen_uv_to_image_sampler = image_texel_to_image_sampler *
image_data_to_image_texel *
output_data_to_image_data *
output_texel_to_output_data *
output_screen_uv_to_output_texel;
return output_screen_uv_to_image_sampler;
}
void image_sync()
{
state.image = space_->get_image(main_);
if (state.image == nullptr) {
/* Early exit, nothing to draw. */
return;
}
state.flags.do_tile_drawing = state.image->source != IMA_SRC_TILED &&
space_->use_tile_drawing();
void *lock;
ImBuf *image_buffer = space_->acquire_image_buffer(state.image, &lock);
const float2 image_size = float2(image_buffer ? image_buffer->x : 1024.0f,
image_buffer ? image_buffer->y : 1024.0f);
float2 offset = float2(0.0f);
if (image_buffer && space_->use_display_window() &&
flag_is_set(image_buffer->flags, ImBufFlags::HasDisplayWindow))
{
offset = float2(image_buffer->display_offset);
}
state.ss_to_texture = this->compute_screen_space_to_sampler_space_transformation(
float2(region->winx, region->winy),
offset,
image_size,
space_->get_pan_offset(),
space_->get_zoom(),
space_->get_aspect_ratio());
const Scene *scene = DRW_context_get()->scene;
state.sh_params.update(space_.get(), scene, state.image, image_buffer);
space_->release_buffer(state.image, image_buffer, lock);
ImageUser *iuser = space_->get_image_user();
if (state.image->rr != nullptr) {
BKE_image_multilayer_index(state.image->rr, iuser);
}
else {
BKE_image_multiview_index(state.image, iuser);
}
}
void object_sync(ObjectRef & /*obref*/, Manager & /*manager*/) final {}
void end_sync() final {}
void draw(Manager & /*manager*/) final
{
DRW_submission_start();
if (drawing_mode_) {
drawing_mode_->draw_viewport();
}
else {
GPU_framebuffer_clear_color_depth(
DRW_context_get()->viewport_framebuffer_list_get()->default_fb, double4(0.0), 1.0f);
}
this->state.float_buffers.remove_unused_buffers();
state.image = nullptr;
drawing_mode_.reset();
DRW_submission_end();
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BKE_image.hh"
#include "BKE_image_partial_update.hh"
namespace blender {
struct PartialImageUpdater {
PartialUpdateUser *user;
const Image *image;
/**
* \brief Ensure that there is a partial update user for the given image.
*/
void ensure_image(const Image *new_image)
{
if (!is_valid(new_image)) {
free();
create(new_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 *new_image)
{
BLI_assert(user == nullptr);
user = BKE_image_partial_update_create(new_image);
image = new_image;
}
void free()
{
if (user != nullptr) {
BKE_image_partial_update_free(user);
user = nullptr;
image = nullptr;
}
}
};
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include <optional>
#include "BKE_image.hh"
#include "image_state.hh"
#include "image_texture_info.hh"
namespace blender {
/* Forward declarations */
extern "C" {
struct Image;
}
/* *********** LISTS *********** */
namespace image_engine {
/**
* Abstract class for a drawing mode of the image engine.
*
* The drawing mode decides how to draw the image on the screen. Each way how to draw would have
* its own subclass.
*/
class AbstractDrawingMode {
public:
virtual ~AbstractDrawingMode() = default;
virtual void begin_sync() const = 0;
virtual void image_sync(blender::Image *image, blender::ImageUser *iuser) const = 0;
virtual void draw_viewport() const = 0;
};
} // namespace image_engine
} // namespace blender

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#include "image_shader.hh"
namespace blender::image_engine {
ShaderModule *ShaderModule::g_shader_module = nullptr;
ShaderModule &ShaderModule::module_get()
{
if (g_shader_module == nullptr) {
g_shader_module = new ShaderModule();
}
return *g_shader_module;
}
void ShaderModule::module_free()
{
delete g_shader_module;
g_shader_module = nullptr;
}
} // namespace blender::image_engine

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "DRW_render.hh"
#include "GPU_shader.hh"
namespace blender::image_engine {
/**
* Shader module. Shared between instances.
*/
class ShaderModule {
private:
struct ShaderDeleter {
void operator()(gpu::Shader *shader)
{
GPU_SHADER_FREE_SAFE(shader);
}
};
using ShaderPtr = std::unique_ptr<gpu::Shader, ShaderDeleter>;
/** Shared shader module across all engine instances. */
static ShaderModule *g_shader_module;
public:
/** Shaders */
ShaderPtr depth = shader("image_engine_depth_shader");
ShaderPtr color = shader("image_engine_color_shader");
ShaderPtr image = shader("image_engine_image_shader");
ShaderPtr image_tiled = shader("image_engine_image_tiled_shader");
/** Module */
/** Only to be used by Instance constructor. */
static ShaderModule &module_get();
static void module_free();
private:
ShaderPtr shader(const char *create_info_name)
{
return ShaderPtr(GPU_shader_create_from_info_name(create_info_name));
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,53 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "DNA_camera_types.h"
#include "DNA_image_types.h"
#include "DNA_scene_types.h"
#include "BLI_math_vector.h"
#include "IMB_imbuf_types.hh"
#include "BKE_image.hh"
#include "DRW_render.hh"
#include "image_shader_shared.hh"
#include "image_space.hh"
namespace blender::image_engine {
struct ShaderParameters {
eImageDrawFlags flags = IMAGE_DRAW_FLAG_DEFAULT;
float4 shuffle;
float2 far_near;
bool use_premul_alpha = false;
void update(AbstractSpaceAccessor *space,
const Scene *scene,
blender::Image *image,
ImBuf *image_buffer)
{
flags = IMAGE_DRAW_FLAG_DEFAULT;
shuffle = float4(1.0f);
far_near = float2(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) {
const Camera &camera = DRW_object_get_data_for_drawing<const Camera>(*scene->camera);
far_near = float2(camera.clip_end, camera.clip_start);
}
space->get_shader_parameters(*this, image_buffer);
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_shader_shared_utils.hh"
enum [[host_shared]] eImageDrawFlags : uint32_t {
IMAGE_DRAW_FLAG_DEFAULT = 0,
IMAGE_DRAW_FLAG_SHOW_ALPHA = (1 << 0),
IMAGE_DRAW_FLAG_APPLY_ALPHA = (1 << 1),
IMAGE_DRAW_FLAG_SHUFFLING = (1 << 2),
IMAGE_DRAW_FLAG_DEPTH = (1 << 3),
};
#ifndef GPU_SHADER
ENUM_OPERATORS(eImageDrawFlags)
#endif

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BLI_math_matrix_types.hh"
namespace blender {
struct ARegion;
struct ImBuf;
struct Image;
struct ImageUser;
struct Main;
namespace image_engine {
struct 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 blender::Image *get_image(Main *bmain) = 0;
/**
* Return the #ImageUser of the space.
*
* The return value is optional.
*/
virtual blender::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(blender::Image *image, void **lock) = 0;
/**
* Release a previous locked image from #acquire_image_buffer.
*/
virtual void release_buffer(blender::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) = 0;
/** \brief Is (wrap) repeat option enabled in the space. */
virtual bool use_tile_drawing() const = 0;
/** \brief Draw image with display window offsets. */
virtual bool use_display_window() const = 0;
/** \brief Gets the zoom factor of the space. A factor of 2 is a zoom-in by two times. */
virtual float get_zoom() const = 0;
/** \brief Gets the aspect ratio of the image. The ratio is for the vertical axis. */
virtual float get_aspect_ratio() const = 0;
/** \brief Gets the pan offset of the space in image pixel space. */
virtual float2 get_pan_offset() const = 0;
};
} // namespace image_engine
} // namespace blender

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "ED_image.hh"
#include "DNA_screen_types.h"
#include "image_private.hh"
#include "image_shader_shared.hh"
namespace blender::image_engine {
class SpaceImageAccessor : public AbstractSpaceAccessor {
SpaceImage *sima;
public:
SpaceImageAccessor(SpaceImage *sima) : sima(sima) {}
blender::Image *get_image(Main * /*bmain*/) override
{
return ED_space_image(sima);
}
ImageUser *get_image_user() override
{
return &sima->iuser;
}
ImBuf *acquire_image_buffer(blender::Image * /*image*/, void **lock) override
{
return ED_space_image_acquire_buffer(sima, lock, 0, false);
}
void release_buffer(blender::Image * /*image*/, ImBuf *image_buffer, void *lock) override
{
ED_space_image_release_buffer(sima, image_buffer, lock);
}
void get_shader_parameters(ShaderParameters &r_shader_parameters, ImBuf *image_buffer) override
{
const int sima_flag = sima->flag & ED_space_image_get_display_channel_mask(image_buffer);
if ((sima_flag & SI_USE_ALPHA) != 0) {
/* Show RGBA */
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHOW_ALPHA | IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
else if ((sima_flag & SI_SHOW_ALPHA) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
r_shader_parameters.shuffle = float4(0.0f, 0.0f, 0.0f, 1.0f);
}
else if ((sima_flag & SI_SHOW_ZBUF) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_DEPTH | IMAGE_DRAW_FLAG_SHUFFLING;
r_shader_parameters.shuffle = float4(1.0f, 0.0f, 0.0f, 0.0f);
}
else if ((sima_flag & SI_SHOW_R) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(image_buffer)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(1.0f, 0.0f, 0.0f, 0.0f);
}
else if ((sima_flag & SI_SHOW_G) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(image_buffer)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(0.0f, 1.0f, 0.0f, 0.0f);
}
else if ((sima_flag & SI_SHOW_B) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(image_buffer)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(0.0f, 0.0f, 1.0f, 0.0f);
}
else /* RGB */ {
if (IMB_alpha_affects_rgb(image_buffer)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
}
}
bool use_tile_drawing() const override
{
return (sima->flag & SI_DRAW_TILE) != 0;
}
bool use_display_window() const override
{
return sima->mode == SI_MODE_VIEW;
}
float get_zoom() const override
{
return this->sima->zoom;
}
float get_aspect_ratio() const override
{
float2 aspect_ratio;
ED_space_image_get_aspect(this->sima, &aspect_ratio.x, &aspect_ratio.y);
return aspect_ratio.y;
}
float2 get_pan_offset() const override
{
/* The offsets are stored with zooming, so retrieve original offsets by multiplying the zoom.
* Furthermore, take the negatives since we want the offset of the image, not the space. */
return -float2(sima->xof, sima->yof) * sima->zoom;
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,108 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "image_private.hh"
namespace blender::image_engine {
class SpaceNodeAccessor : public AbstractSpaceAccessor {
SpaceNode *snode;
public:
SpaceNodeAccessor(SpaceNode *snode) : snode(snode) {}
blender::Image *get_image(Main *bmain) override
{
return BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
}
ImageUser *get_image_user() override
{
return nullptr;
}
ImBuf *acquire_image_buffer(blender::Image *image, void **lock) override
{
return BKE_image_acquire_ibuf_gpu(image, nullptr, lock);
}
void release_buffer(blender::Image *image, ImBuf *ibuf, void *lock) override
{
BKE_image_release_ibuf(image, ibuf, lock);
}
void get_shader_parameters(ShaderParameters &r_shader_parameters, ImBuf *ibuf) override
{
if ((snode->flag & SNODE_USE_ALPHA) != 0) {
/* Show RGBA */
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHOW_ALPHA | IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
else if ((snode->flag & SNODE_SHOW_ALPHA) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
r_shader_parameters.shuffle = float4(0.0f, 0.0f, 0.0f, 1.0f);
}
else if ((snode->flag & SNODE_SHOW_R) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(ibuf)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(1.0f, 0.0f, 0.0f, 0.0f);
}
else if ((snode->flag & SNODE_SHOW_G) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(ibuf)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(0.0f, 1.0f, 0.0f, 0.0f);
}
else if ((snode->flag & SNODE_SHOW_B) != 0) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_SHUFFLING;
if (IMB_alpha_affects_rgb(ibuf)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
r_shader_parameters.shuffle = float4(0.0f, 0.0f, 1.0f, 0.0f);
}
else /* RGB */ {
if (IMB_alpha_affects_rgb(ibuf)) {
r_shader_parameters.flags |= IMAGE_DRAW_FLAG_APPLY_ALPHA;
}
}
}
bool use_tile_drawing() const override
{
return false;
}
bool use_display_window() const override
{
return true;
}
float get_zoom() const override
{
return this->snode->zoom;
}
float get_aspect_ratio() const override
{
return 1.0f;
}
float2 get_pan_offset() const override
{
return float2(snode->xof, snode->yof);
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BLI_math_matrix_types.hh"
#include "BKE_image_wrappers.hh"
#include "image_batches.hh"
#include "image_buffer_cache.hh"
#include "image_partial_updater.hh"
#include "image_private.hh"
#include "image_shader_params.hh"
#include "image_texture_info.hh"
#include "image_usage.hh"
#include "DRW_render.hh"
#include "draw_command.hh"
#include "draw_manager.hh"
#include "draw_pass.hh"
namespace blender::image_engine {
using namespace blender::draw;
struct State {
blender::Image *image = nullptr;
/** Usage data of the previous time, to identify changes that require a full update. */
ImageUsage last_usage;
PartialImageUpdater partial_update = {};
View view = {"Image.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;
Framebuffer depth_fb = {"Image.Depth"};
Framebuffer color_fb = {"Image.Color"};
PassSimple depth_ps = {"Image.Depth"};
PassSimple image_ps = {"Image.Color"};
/**
* Cache containing the float buffers when drawing byte images.
*/
FloatBufferCache float_buffers;
/** \brief Transform matrix to convert a normalized screen space coordinates to texture space. */
float3x3 ss_to_texture;
Vector<TextureInfo> texture_infos;
public:
virtual ~State() = default;
void clear_need_full_update_flag()
{
reset_need_full_update(false);
}
void mark_all_texture_slots_dirty()
{
reset_need_full_update(true);
}
void update_batches()
{
for (TextureInfo &info : texture_infos) {
BatchUpdater batch_updater(info);
batch_updater.update_batch();
}
}
void update_image_usage(const ImageUser *image_user)
{
ImageUsage usage(image, image_user, flags.do_tile_drawing);
if (last_usage != usage) {
last_usage = usage;
reset_need_full_update(true);
float_buffers.clear();
}
}
private:
/** \brief Set dirty flag of all texture slots to the given value. */
void reset_need_full_update(bool new_value)
{
for (TextureInfo &info : texture_infos) {
info.need_full_update = new_value;
}
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include "BLI_math_matrix.hh"
#include "BLI_rect.h"
#include "GPU_batch.hh"
#include "GPU_texture.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
namespace blender::image_engine {
using namespace blender::draw;
struct TextureInfo : NonCopyable {
/**
* \brief does this texture need a full update.
*
* When set to false the texture can be updated using a partial update.
*/
bool need_full_update : 1;
/** \brief area of the texture in screen space. */
rcti clipping_bounds;
/** \brief uv area of the texture in screen space. */
rctf clipping_uv_bounds;
/* Which tile of the screen is used with this texture. Used to safely calculate the correct
* offset of the textures. */
int2 tile_id;
/**
* \brief Batch to draw the associated text on the screen.
*
* Contains a VBO with `pos` and `uv`.
* `pos` (2xI32) is relative to the origin of the space.
* `uv` (2xF32) reflect the uv bounds.
*/
gpu::Batch *batch = nullptr;
/**
* \brief GPU Texture for a partial region of the image editor.
*/
Texture texture = {"Image.Tile"};
int2 last_texture_size = int2(0);
TextureInfo() = default;
TextureInfo(TextureInfo &&other) = default;
~TextureInfo()
{
if (batch != nullptr) {
GPU_batch_discard(batch);
batch = nullptr;
}
}
/**
* \brief return the offset of the texture with the area.
*
* A texture covers only a part of the area. The offset if the offset in screen coordinates
* between the area and the part that the texture covers.
*/
int2 offset() const
{
return int2(clipping_bounds.xmin, clipping_bounds.ymin);
}
void ensure_gpu_texture(int2 texture_size)
{
const bool is_allocated = texture.is_valid();
const bool resolution_changed = assign_if_different(last_texture_size, texture_size);
const bool should_be_freed = is_allocated && resolution_changed;
const bool should_be_created = !is_allocated || resolution_changed;
if (should_be_freed) {
texture.free();
}
if (should_be_created) {
texture.ensure_2d(
gpu::TextureFormat::SFLOAT_16_16_16_16, texture_size, GPU_TEXTURE_USAGE_SHADER_READ);
}
need_full_update |= should_be_created;
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,63 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#pragma once
#include <cstring>
#include "DNA_color_types.h"
#include "DNA_image_types.h"
namespace blender::image_engine {
/**
* ImageUsage contains data of the image and image user to identify changes that require a rebuild
* the texture slots.
*/
struct ImageUsage {
/** Render pass of the image that is used. */
short pass = 0;
/** Layer of the image that is used. */
short layer = 0;
/** View of the image that is used. */
short view = 0;
ColorManagedColorspaceSettings colorspace_settings;
/** IMA_ALPHA_* */
char alpha_mode;
bool last_tile_drawing;
const void *last_image = nullptr;
const void *last_scene = nullptr;
ImageUsage() = default;
ImageUsage(const blender::Image *image,
const blender::ImageUser *image_user,
bool do_tile_drawing)
{
pass = image_user ? image_user->pass : 0;
layer = image_user ? image_user->layer : 0;
view = image_user ? image_user->multi_index : 0;
colorspace_settings = image->colorspace_settings;
alpha_mode = image->alpha_mode;
last_image = static_cast<const void *>(image);
last_scene = image_user ? static_cast<const void *>(image_user->scene) : nullptr;
last_tile_drawing = do_tile_drawing;
}
bool operator==(const ImageUsage &other) const
{
return memcmp(this, &other, sizeof(ImageUsage)) == 0;
}
bool operator!=(const ImageUsage &other) const
{
return !(*this == other);
}
};
} // namespace blender::image_engine

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/engine_image_infos.hh"
#include "draw_colormanagement_lib.glsl"
#include "image_engine_lib.glsl"
void main()
{
int2 uvs_clamped = int2(uv_screen);
float depth = texelFetch(depth_tx, uvs_clamped, 0).r;
if (depth == 1.0f) {
gpu_discard_fragment();
return;
}
float4 tex_color = texelFetch(image_tx, uvs_clamped - offset, 0);
out_color = image_engine_apply_parameters(
tex_color, draw_flags, is_image_premultiplied, shuffle, FAR_DISTANCE, NEAR_DISTANCE);
}

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "draw_model_lib.glsl"
#include "draw_view_lib.glsl"
void main()
{
float3 image_pos = float3(pos.x, pos.y, 0.0f);
uv_screen = image_pos.xy;
float4 position = drw_point_world_to_homogenous(image_pos);
position.z = 0.0f;
gl_Position = position;
}

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/engine_image_infos.hh"
#include "draw_colormanagement_lib.glsl"
#include "image_engine_lib.glsl"
bool is_border(float2 uv)
{
return (uv.x < min_max_uv.x || uv.y < min_max_uv.y || uv.x >= min_max_uv.z ||
uv.y >= min_max_uv.w);
}
void main()
{
bool border = is_border(uv_image);
gl_FragDepth = border ? Z_DEPTH_BORDER : Z_DEPTH_IMAGE;
}

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "draw_model_lib.glsl"
#include "draw_view_lib.glsl"
void main()
{
float3 image_pos = float3(pos.x, pos.y, 0.0f);
uv_image = uv;
float4 position = drw_point_world_to_homogenous(image_pos);
position.z = 0.0f;
gl_Position = position;
}

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/engine_image_infos.hh"
#include "gpu_shader_math_matrix_transform_lib.glsl"
#include "image_engine_lib.glsl"
void main()
{
const float2 coordinates = transform_point(to_float3x3(image_matrix), screen_uv);
if (!is_repeated &&
(any(lessThan(coordinates, float2(0.0))) || any(greaterThan(coordinates, float2(1.0)))))
{
out_color = float4(0.0f);
gl_FragDepth = Z_DEPTH_BORDER;
return;
}
const float4 image_color = texture(image_tx, coordinates);
out_color = image_engine_apply_parameters(
image_color, draw_flags, is_image_premultiplied, shuffle, FAR_DISTANCE, NEAR_DISTANCE);
gl_FragDepth = Z_DEPTH_IMAGE;
}

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/engine_image_infos.hh"
#include "gpu_shader_math_matrix_transform_lib.glsl"
#include "gpu_shader_tiled_image_lookup_lib.glsl"
#include "image_engine_lib.glsl"
void main()
{
const float2 coordinates = transform_point(to_float3x3(image_matrix), screen_uv);
float3 tiled_coordinates = float3(coordinates, 0.0f);
if (!tiled_image_lookup(tiled_coordinates, image_tile_array, image_tile_data)) {
out_color = float4(0.0f);
gl_FragDepth = Z_DEPTH_BORDER;
return;
}
const float4 image_color = texture(image_tile_array, tiled_coordinates);
out_color = image_engine_apply_parameters(
image_color, draw_flags, is_image_premultiplied, shuffle, FAR_DISTANCE, NEAR_DISTANCE);
gl_FragDepth = Z_DEPTH_IMAGE;
}

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "image_shader_shared.hh"
#define Z_DEPTH_BORDER 1.0f
#define Z_DEPTH_IMAGE 0.75f
#define FAR_DISTANCE far_near_distances.x
#define NEAR_DISTANCE far_near_distances.y
float4 image_engine_apply_parameters(float4 color,
int flags,
bool is_image_premultiplied,
float4 shuffle_color,
float far_distance,
float near_distance)
{
float4 result = color;
if ((flags & IMAGE_DRAW_FLAG_APPLY_ALPHA) != 0) {
if (!is_image_premultiplied) {
result.rgb *= result.a;
}
}
if ((flags & IMAGE_DRAW_FLAG_DEPTH) != 0) {
result = smoothstep(far_distance, near_distance, result);
}
if ((flags & IMAGE_DRAW_FLAG_SHUFFLING) != 0) {
result = float4(dot(result, shuffle_color));
}
if ((flags & IMAGE_DRAW_FLAG_SHOW_ALPHA) == 0) {
result.a = 1.0f;
}
return result;
}

View File

@@ -0,0 +1,78 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef GPU_SHADER
# pragma once
# include "draw_view_infos.hh"
# include "gpu_shader_fullscreen_infos.hh"
#endif
#include "gpu_shader_create_info.hh"
GPU_SHADER_INTERFACE_INFO(image_engine_color_iface)
SMOOTH(float2, uv_screen)
GPU_SHADER_INTERFACE_END()
GPU_SHADER_CREATE_INFO(image_engine_color_shader)
VERTEX_IN(0, int2, pos)
VERTEX_OUT(image_engine_color_iface)
FRAGMENT_OUT(0, float4, out_color)
PUSH_CONSTANT(float4, shuffle)
PUSH_CONSTANT(float2, far_near_distances)
PUSH_CONSTANT(int2, offset)
PUSH_CONSTANT(int, draw_flags)
PUSH_CONSTANT(bool, is_image_premultiplied)
SAMPLER(0, sampler2D, image_tx)
SAMPLER(1, sampler2DDepth, depth_tx)
VERTEX_SOURCE("image_engine_color_vert.glsl")
FRAGMENT_SOURCE("image_engine_color_frag.glsl")
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_modelmat)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_INTERFACE_INFO(image_engine_depth_iface)
SMOOTH(float2, uv_image)
GPU_SHADER_INTERFACE_END()
GPU_SHADER_CREATE_INFO(image_engine_depth_shader)
VERTEX_IN(0, int2, pos)
VERTEX_IN(1, float2, uv)
VERTEX_OUT(image_engine_depth_iface)
PUSH_CONSTANT(float4, min_max_uv)
VERTEX_SOURCE("image_engine_depth_vert.glsl")
FRAGMENT_SOURCE("image_engine_depth_frag.glsl")
ADDITIONAL_INFO(draw_view)
ADDITIONAL_INFO(draw_modelmat)
DEPTH_WRITE(DepthWrite::ANY)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(image_engine_image_shared)
FRAGMENT_OUT(0, float4, out_color)
PUSH_CONSTANT(float4x4, image_matrix)
PUSH_CONSTANT(float4, shuffle)
PUSH_CONSTANT(float2, far_near_distances)
PUSH_CONSTANT(int, draw_flags)
PUSH_CONSTANT(bool, is_image_premultiplied)
ADDITIONAL_INFO(gpu_fullscreen)
DEPTH_WRITE(DepthWrite::ANY)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(image_engine_image_shader)
ADDITIONAL_INFO(image_engine_image_shared)
PUSH_CONSTANT(bool, is_repeated)
SAMPLER(0, sampler2D, image_tx)
FRAGMENT_SOURCE("image_engine_image_frag.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(image_engine_image_tiled_shader)
ADDITIONAL_INFO(image_engine_image_shared)
SAMPLER(0, sampler2DArray, image_tile_array)
SAMPLER(1, sampler1DArray, image_tile_data)
FRAGMENT_SOURCE("image_engine_image_tiled_frag.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()