Refactor: Change functions to retrieve GPU textures from images

* For materials with UDIM tiles support, get array and mapping in one call
* For viewers that can use render results, add a dedicated function
* Fix potential use of render results in stencil overlay and grease pencil

Pull Request: https://projects.blender.org/blender/blender/pulls/117563
This commit is contained in:
Brecht Van Lommel 2024-02-01 20:32:24 +01:00 committed by Brecht Van Lommel
parent e6acd167e9
commit abf4c4d9ef
14 changed files with 112 additions and 140 deletions

View File

@ -518,8 +518,7 @@ void BKE_image_ensure_gpu_texture(struct Image *image, struct ImageUser *iuser);
/**
* Get the #GPUTexture for a given `Image`.
*
* `iuser` and `ibuf` are mutual exclusive parameters. The caller can pass the `ibuf` when already
* available. It is also required when requesting the #GPUTexture for a render result.
*
*
* The requested GPU texture will be cached for subsequent calls, but only a single layer, pass,
* and view can be cached at a time, so the cache should be invalidated in operators and RNA
@ -530,11 +529,26 @@ void BKE_image_ensure_gpu_texture(struct Image *image, struct ImageUser *iuser);
* calling BKE_image_ensure_gpu_texture. This is a workaround until image can support a more
* complete caching system.
*/
struct GPUTexture *BKE_image_get_gpu_texture(struct Image *image,
struct ImageUser *iuser,
struct ImBuf *ibuf);
struct GPUTexture *BKE_image_get_gpu_tiles(struct Image *image, struct ImageUser *iuser);
struct GPUTexture *BKE_image_get_gpu_tilemap(struct Image *image, struct ImageUser *iuser);
struct GPUTexture *BKE_image_get_gpu_texture(struct Image *image, struct ImageUser *iuser);
/*
* Like BKE_image_get_gpu_texture, but can also get render or compositing result.
*/
struct GPUTexture *BKE_image_get_gpu_viewer_texture(struct Image *image, struct ImageUser *iuser);
/*
* Like BKE_image_get_gpu_texture, but can also return array and tile mapping texture for UDIM
* tiles as used in material shaders.
*/
typedef struct ImageGPUTextures {
struct GPUTexture *texture;
struct GPUTexture *tile_mapping;
} ImageGPUTextures;
ImageGPUTextures BKE_image_get_gpu_material_texture(struct Image *image,
struct ImageUser *iuser,
const bool use_tile_mapping);
/**
* Is the alpha of the `GPUTexture` for a given image/ibuf premultiplied.
*/

View File

@ -351,13 +351,15 @@ void BKE_image_ensure_gpu_texture(Image *image, ImageUser *image_user)
}
}
static GPUTexture *image_get_gpu_texture(Image *ima,
ImageUser *iuser,
ImBuf *ibuf,
eGPUTextureTarget textarget)
static ImageGPUTextures image_get_gpu_texture(Image *ima,
ImageUser *iuser,
const bool use_viewers,
const bool use_tile_mapping)
{
ImageGPUTextures result = {};
if (ima == nullptr) {
return nullptr;
return result;
}
/* Free any unused GPU textures, since we know we are in a thread with OpenGL
@ -396,6 +398,11 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
/* Tag as in active use for garbage collector. */
BKE_image_tag_time(ima);
/* Test if we need to get a tiled array texture. */
eGPUTextureTarget textarget = (use_tile_mapping && ima->source == IMA_SRC_TILED) ?
TEXTARGET_2D_ARRAY :
TEXTARGET_2D;
/* Test if we already have a texture. */
int current_view = iuser ? iuser->multi_index : 0;
if (current_view >= 2) {
@ -403,7 +410,9 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
}
GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view);
if (*tex) {
return *tex;
result.texture = *tex;
result.tile_mapping = *get_image_gpu_texture_ptr(ima, TEXTARGET_TILE_MAPPING, current_view);
return result;
}
/* Check if we have a valid image. If not, we return a dummy
@ -411,32 +420,38 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
ImageTile *tile = BKE_image_get_tile(ima, 0);
if (tile == nullptr) {
*tex = image_gpu_texture_error_create(textarget);
return *tex;
result.texture = *tex;
return result;
}
/* check if we have a valid image buffer */
ImBuf *ibuf_intern = ibuf;
if (ibuf_intern == nullptr) {
ibuf_intern = BKE_image_acquire_ibuf(ima, iuser, nullptr);
if (ibuf_intern == nullptr) {
*tex = image_gpu_texture_error_create(textarget);
return *tex;
}
void *lock;
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, (use_viewers) ? &lock : nullptr);
if (ibuf == nullptr) {
BKE_image_release_ibuf(ima, ibuf, (use_viewers) ? &lock : nullptr);
*tex = image_gpu_texture_error_create(textarget);
result.texture = *tex;
return result;
}
if (textarget == TEXTARGET_2D_ARRAY) {
*tex = gpu_texture_create_tile_array(ima, ibuf_intern);
}
else if (textarget == TEXTARGET_TILE_MAPPING) {
*tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0);
/* For materials, array and tile mapping in case there are UDIM tiles. */
*tex = gpu_texture_create_tile_array(ima, ibuf);
result.texture = *tex;
GPUTexture **tile_mapping_tex = get_image_gpu_texture_ptr(
ima, TEXTARGET_TILE_MAPPING, current_view);
*tile_mapping_tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0);
result.tile_mapping = *tile_mapping_tex;
}
else {
/* Single image texture. */
const bool use_high_bitdepth = (ima->flag & IMA_HIGH_BITDEPTH);
const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima,
ibuf_intern);
const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf);
*tex = IMB_create_gpu_texture(
ima->id.name + 2, ibuf_intern, use_high_bitdepth, store_premultiplied);
*tex = IMB_create_gpu_texture(ima->id.name + 2, ibuf, use_high_bitdepth, store_premultiplied);
result.texture = *tex;
if (*tex) {
GPU_texture_extend_mode(*tex, GPU_SAMPLER_EXTEND_MODE_REPEAT);
@ -455,29 +470,29 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
}
if (*tex) {
GPU_texture_original_size_set(*tex, ibuf_intern->x, ibuf_intern->y);
GPU_texture_original_size_set(*tex, ibuf->x, ibuf->y);
}
if (ibuf != ibuf_intern) {
BKE_image_release_ibuf(ima, ibuf_intern, nullptr);
}
BKE_image_release_ibuf(ima, ibuf, (use_viewers) ? &lock : nullptr);
return *tex;
return result;
}
GPUTexture *BKE_image_get_gpu_texture(Image *image, ImageUser *iuser, ImBuf *ibuf)
GPUTexture *BKE_image_get_gpu_texture(Image *image, ImageUser *iuser)
{
return image_get_gpu_texture(image, iuser, ibuf, TEXTARGET_2D);
return image_get_gpu_texture(image, iuser, false, false).texture;
}
GPUTexture *BKE_image_get_gpu_tiles(Image *image, ImageUser *iuser)
GPUTexture *BKE_image_get_gpu_viewer_texture(Image *image, ImageUser *iuser)
{
return image_get_gpu_texture(image, iuser, nullptr, TEXTARGET_2D_ARRAY);
return image_get_gpu_texture(image, iuser, true, false).texture;
}
GPUTexture *BKE_image_get_gpu_tilemap(Image *image, ImageUser *iuser)
ImageGPUTextures BKE_image_get_gpu_material_texture(Image *image,
ImageUser *iuser,
const bool use_tile_mapping)
{
return image_get_gpu_texture(image, iuser, nullptr, TEXTARGET_TILE_MAPPING);
return image_get_gpu_texture(image, iuser, false, use_tile_mapping);
}
/** \} */

View File

@ -71,7 +71,7 @@ LookdevWorld::LookdevWorld()
/* TODO: This works around the issue that the first time the texture is accessed the image would
* overwrite the set GPU texture. A better solution would be to use image data-blocks as part of
* the studio-lights, but that requires a larger refactoring. */
BKE_image_get_gpu_texture(&image, &environment_storage->iuser, nullptr);
BKE_image_get_gpu_texture(&image, &environment_storage->iuser);
/* Create a dummy image data block to hold GPU textures generated by studio-lights. */
STRNCPY(world.id.name, "WOLookdev");

View File

@ -41,18 +41,11 @@ static GPENCIL_MaterialPool *gpencil_material_pool_add(GPENCIL_PrivateData *pd)
static GPUTexture *gpencil_image_texture_get(Image *image, bool *r_alpha_premult)
{
ImBuf *ibuf;
ImageUser iuser = {nullptr};
GPUTexture *gpu_tex = nullptr;
void *lock;
ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
if (ibuf != nullptr && ibuf->byte_buffer.data != nullptr) {
gpu_tex = BKE_image_get_gpu_texture(image, &iuser, ibuf);
*r_alpha_premult = (image->alpha_mode == IMA_ALPHA_PREMUL);
}
BKE_image_release_ibuf(image, ibuf, lock);
gpu_tex = BKE_image_get_gpu_texture(image, &iuser);
*r_alpha_premult = (gpu_tex) ? (image->alpha_mode == IMA_ALPHA_PREMUL) : false;
return gpu_tex;
}

View File

@ -89,10 +89,8 @@ class MaterialModule {
/* Returns the correct flag for this texture. */
gpMaterialFlag texture_sync(::Image *image, gpMaterialFlag use_flag, gpMaterialFlag premul_flag)
{
ImBuf *ibuf;
ImageUser iuser = {nullptr};
GPUTexture *gpu_tex = nullptr;
void *lock;
bool premul = false;
if (image == nullptr) {
@ -100,13 +98,10 @@ class MaterialModule {
return GP_FLAG_NONE;
}
ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
if (ibuf != nullptr) {
gpu_tex = BKE_image_get_gpu_texture(image, &iuser, ibuf);
gpu_tex = BKE_image_get_gpu_texture(image, &iuser);
if (gpu_tex) {
premul = (image->alpha_mode == IMA_ALPHA_PREMUL) != 0;
}
BKE_image_release_ibuf(image, ibuf, lock);
texture_pool_.append(gpu_tex);

View File

@ -354,24 +354,15 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata)
if (pd->edit_uv.do_stencil_overlay) {
const Brush *brush = BKE_paint_brush(&ts->imapaint.paint);
::Image *stencil_image = brush->clone.image;
ImBuf *stencil_ibuf = BKE_image_acquire_ibuf(
stencil_image, nullptr, &pd->edit_uv.stencil_lock);
GPUTexture *stencil_texture = BKE_image_get_gpu_texture(stencil_image, nullptr);
if (stencil_ibuf == nullptr) {
pd->edit_uv.stencil_ibuf = nullptr;
pd->edit_uv.stencil_image = nullptr;
}
else {
if (stencil_texture != nullptr) {
DRW_PASS_CREATE(psl->edit_uv_stencil_ps,
DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS |
DRW_STATE_BLEND_ALPHA_PREMUL);
GPUShader *sh = OVERLAY_shader_edit_uv_stencil_image();
GPUBatch *geom = DRW_cache_quad_get();
DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->edit_uv_stencil_ps);
pd->edit_uv.stencil_ibuf = stencil_ibuf;
pd->edit_uv.stencil_image = stencil_image;
GPUTexture *stencil_texture = BKE_image_get_gpu_texture(
stencil_image, nullptr, stencil_ibuf);
DRW_shgroup_uniform_texture(grp, "imgTexture", stencil_texture);
DRW_shgroup_uniform_bool_copy(grp, "imgPremultiplied", true);
DRW_shgroup_uniform_bool_copy(grp, "imgAlphaBlend", true);
@ -380,7 +371,8 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata)
float size_image[2];
BKE_image_get_size_fl(image, nullptr, size_image);
float size_stencil_image[2] = {float(stencil_ibuf->x), float(stencil_ibuf->y)};
float size_stencil_image[2] = {float(GPU_texture_original_width(stencil_texture)),
float(GPU_texture_original_height(stencil_texture))};
float obmat[4][4];
unit_m4(obmat);
@ -392,10 +384,6 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata)
DRW_shgroup_call_obmat(grp, geom, obmat);
}
}
else {
pd->edit_uv.stencil_ibuf = nullptr;
pd->edit_uv.stencil_image = nullptr;
}
if (pd->edit_uv.do_mask_overlay) {
const bool is_combined_overlay = pd->edit_uv.mask_overlay_mode == MASK_OVERLAY_COMBINED;
@ -545,13 +533,6 @@ static void OVERLAY_edit_uv_draw_finish(OVERLAY_Data *vedata)
OVERLAY_StorageList *stl = vedata->stl;
OVERLAY_PrivateData *pd = stl->pd;
if (pd->edit_uv.stencil_ibuf) {
BKE_image_release_ibuf(
pd->edit_uv.stencil_image, pd->edit_uv.stencil_ibuf, pd->edit_uv.stencil_lock);
pd->edit_uv.stencil_image = nullptr;
pd->edit_uv.stencil_ibuf = nullptr;
}
DRW_TEXTURE_FREE_SAFE(pd->edit_uv.mask_texture);
}

View File

@ -129,7 +129,6 @@ static GPUTexture *image_camera_background_texture_get(CameraBGImage *bgpic,
bool *r_use_alpha_premult,
bool *r_use_view_transform)
{
void *lock;
Image *image = bgpic->ima;
ImageUser *iuser = &bgpic->iuser;
MovieClip *clip = nullptr;
@ -158,22 +157,16 @@ static GPUTexture *image_camera_background_texture_get(CameraBGImage *bgpic,
camera_background_images_stereo_setup(scene, draw_ctx->v3d, image, iuser);
iuser->scene = draw_ctx->scene;
ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, &lock);
if (ibuf == nullptr) {
BKE_image_release_ibuf(image, ibuf, lock);
iuser->scene = nullptr;
return nullptr;
}
width = ibuf->x;
height = ibuf->y;
tex = BKE_image_get_gpu_texture(image, iuser, ibuf);
BKE_image_release_ibuf(image, ibuf, lock);
tex = BKE_image_get_gpu_viewer_texture(image, iuser);
iuser->scene = nullptr;
if (tex == nullptr) {
return nullptr;
}
width = GPU_texture_original_width(tex);
height = GPU_texture_original_height(tex);
aspect_x = bgpic->ima->aspx;
aspect_y = bgpic->ima->aspy;
break;
@ -381,7 +374,7 @@ void OVERLAY_image_empty_cache_populate(OVERLAY_Data *vedata, Object *ob)
if (ima != nullptr) {
ImageUser iuser = *ob->iuser;
camera_background_images_stereo_setup(draw_ctx->scene, draw_ctx->v3d, ima, &iuser);
tex = BKE_image_get_gpu_texture(ima, &iuser, nullptr);
tex = BKE_image_get_gpu_texture(ima, &iuser);
if (tex) {
size[0] = GPU_texture_original_width(tex);
size[1] = GPU_texture_original_height(tex);

View File

@ -139,7 +139,7 @@ void OVERLAY_paint_cache_init(OVERLAY_Data *vedata)
state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_ALPHA;
DRW_PASS_CREATE(psl->paint_color_ps, state | pd->clipping_state);
GPUTexture *tex = BKE_image_get_gpu_texture(imapaint->stencil, nullptr, nullptr);
GPUTexture *tex = BKE_image_get_gpu_texture(imapaint->stencil, nullptr);
const bool mask_premult = (imapaint->stencil->alpha_mode == IMA_ALPHA_PREMUL);
const bool mask_inverted = (imapaint->flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) != 0;

View File

@ -409,11 +409,6 @@ struct OVERLAY_PrivateData {
ListBase totals;
float total_area_ratio;
/* stencil overlay */
Image *stencil_image;
ImBuf *stencil_ibuf;
void *stencil_lock;
/* mask overlay */
Mask *mask;
eMaskOverlayMode mask_overlay_mode;

View File

@ -66,27 +66,19 @@ PassMain::Sub &MeshPass::get_subpass(
is_empty_ = false;
if (image) {
GPUTexture *texture = nullptr;
GPUTexture *tilemap = nullptr;
if (image->source == IMA_SRC_TILED) {
texture = BKE_image_get_gpu_tiles(image, iuser);
tilemap = BKE_image_get_gpu_tilemap(image, iuser);
}
else {
texture = BKE_image_get_gpu_texture(image, iuser, nullptr);
}
if (texture) {
ImageGPUTextures gputex = BKE_image_get_gpu_material_texture(image, iuser, true);
if (gputex.texture) {
auto add_cb = [&] {
PassMain::Sub *sub_pass = passes_[int(geometry_type)][int(eShaderType::TEXTURE)];
sub_pass = &sub_pass->sub(image->id.name);
if (tilemap) {
sub_pass->bind_texture(WB_TILE_ARRAY_SLOT, texture, sampler_state);
sub_pass->bind_texture(WB_TILE_DATA_SLOT, tilemap);
if (gputex.tile_mapping) {
sub_pass->bind_texture(WB_TILE_ARRAY_SLOT, gputex.texture, sampler_state);
sub_pass->bind_texture(WB_TILE_DATA_SLOT, gputex.tile_mapping);
}
else {
sub_pass->bind_texture(WB_TEXTURE_SLOT, texture, sampler_state);
sub_pass->bind_texture(WB_TEXTURE_SLOT, gputex.texture, sampler_state);
}
sub_pass->push_constant("isImageTile", tilemap != nullptr);
sub_pass->push_constant("isImageTile", gputex.tile_mapping != nullptr);
sub_pass->push_constant("imagePremult", image && image->alpha_mode == IMA_ALPHA_PREMUL);
/* TODO(@pragma37): This setting should be exposed on the user side,
* either as a global parameter (and set it here)
@ -99,8 +91,8 @@ PassMain::Sub &MeshPass::get_subpass(
return sub_pass;
};
return *texture_subpass_map_.lookup_or_add_cb(TextureSubPassKey(texture, geometry_type),
add_cb);
return *texture_subpass_map_.lookup_or_add_cb(
TextureSubPassKey(gputex.texture, geometry_type), add_cb);
}
}

View File

@ -1803,18 +1803,15 @@ void DRW_shgroup_add_material_resources(DRWShadingGroup *grp, GPUMaterial *mater
/* Bind all textures needed by the material. */
LISTBASE_FOREACH (GPUMaterialTexture *, tex, &textures) {
if (tex->ima) {
/* Image */
GPUTexture *gputex;
const bool use_tile_mapping = tex->tiled_mapping_name[0];
ImageUser *iuser = tex->iuser_available ? &tex->iuser : nullptr;
if (tex->tiled_mapping_name[0]) {
gputex = BKE_image_get_gpu_tiles(tex->ima, iuser);
drw_shgroup_material_texture(grp, gputex, tex->sampler_name, tex->sampler_state);
gputex = BKE_image_get_gpu_tilemap(tex->ima, iuser);
drw_shgroup_material_texture(grp, gputex, tex->tiled_mapping_name, tex->sampler_state);
}
else {
gputex = BKE_image_get_gpu_texture(tex->ima, iuser, nullptr);
drw_shgroup_material_texture(grp, gputex, tex->sampler_name, tex->sampler_state);
ImageGPUTextures gputex = BKE_image_get_gpu_material_texture(
tex->ima, iuser, use_tile_mapping);
drw_shgroup_material_texture(grp, gputex.texture, tex->sampler_name, tex->sampler_state);
if (gputex.tile_mapping) {
drw_shgroup_material_texture(
grp, gputex.tile_mapping, tex->tiled_mapping_name, tex->sampler_state);
}
}
else if (tex->colorband) {

View File

@ -923,20 +923,17 @@ template<class T> inline void PassBase<T>::material_set(Manager &manager, GPUMat
for (GPUMaterialTexture *tex : ListBaseWrapper<GPUMaterialTexture>(textures)) {
if (tex->ima) {
/* Image */
const bool use_tile_mapping = tex->tiled_mapping_name[0];
ImageUser *iuser = tex->iuser_available ? &tex->iuser : nullptr;
if (tex->tiled_mapping_name[0]) {
GPUTexture *tiles = BKE_image_get_gpu_tiles(tex->ima, iuser);
manager.acquire_texture(tiles);
bind_texture(tex->sampler_name, tiles, tex->sampler_state);
ImageGPUTextures gputex = BKE_image_get_gpu_material_texture(
tex->ima, iuser, use_tile_mapping);
GPUTexture *tile_map = BKE_image_get_gpu_tilemap(tex->ima, iuser);
manager.acquire_texture(tile_map);
bind_texture(tex->tiled_mapping_name, tile_map, tex->sampler_state);
}
else {
GPUTexture *texture = BKE_image_get_gpu_texture(tex->ima, iuser, nullptr);
manager.acquire_texture(texture);
bind_texture(tex->sampler_name, texture, tex->sampler_state);
manager.acquire_texture(gputex.texture);
bind_texture(tex->sampler_name, gputex.texture, tex->sampler_state);
if (gputex.tile_mapping) {
manager.acquire_texture(gputex.tile_mapping);
bind_texture(tex->tiled_mapping_name, gputex.tile_mapping, tex->sampler_state);
}
}
else if (tex->colorband) {

View File

@ -213,7 +213,7 @@ static int rna_Image_gl_load(
BKE_image_multilayer_index(image->rr, &iuser);
}
GPUTexture *tex = BKE_image_get_gpu_texture(image, &iuser, nullptr);
GPUTexture *tex = BKE_image_get_gpu_texture(image, &iuser);
if (tex == nullptr) {
BKE_reportf(reports, RPT_ERROR, "Failed to load image texture '%s'", image->id.name + 2);

View File

@ -672,7 +672,7 @@ static PyObject *pygpu_texture_from_image(PyObject * /*self*/, PyObject *arg)
ImageUser iuser;
BKE_imageuser_default(&iuser);
GPUTexture *tex = BKE_image_get_gpu_texture(ima, &iuser, nullptr);
GPUTexture *tex = BKE_image_get_gpu_texture(ima, &iuser);
return BPyGPUTexture_CreatePyObject(tex, true);
}