Fix #122714: Workbench: Missing textures not shown in Texture Paint mode with magenta

Missing feature from the Workbench Next port.

Pull Request: https://projects.blender.org/blender/blender/pulls/123034
This commit is contained in:
Miguel Pozo
2024-06-20 18:06:26 +02:00
parent c90b78bb6e
commit d8e710885c
6 changed files with 148 additions and 138 deletions

View File

@@ -121,7 +121,7 @@ class Instance {
return;
}
const ObjectState object_state = ObjectState(scene_state, ob);
const ObjectState object_state = ObjectState(scene_state, resources, ob);
/* Needed for mesh cache validation, to prevent two copies of
* of vertex color arrays from being sent to the GPU (e.g.
@@ -232,16 +232,18 @@ class Instance {
Material &material,
gpu::Batch *batch,
ResourceHandle handle,
::Image *image = nullptr,
GPUSamplerState sampler_state = GPUSamplerState::default_sampler(),
ImageUser *iuser = nullptr)
const MaterialTexture *texture = nullptr,
bool show_missing_texture = false)
{
resources.material_buf.append(material);
int material_index = resources.material_buf.size() - 1;
if (show_missing_texture && (!texture || !texture->gpu.texture)) {
texture = &resources.missing_texture;
}
draw_to_mesh_pass(ob_ref, material.is_transparent(), [&](MeshPass &mesh_pass) {
mesh_pass.get_subpass(eGeometryType::MESH, image, sampler_state, iuser)
.draw(batch, handle, material_index);
mesh_pass.get_subpass(eGeometryType::MESH, texture).draw(batch, handle, material_index);
});
}
@@ -271,14 +273,12 @@ class Instance {
Material mat = get_material(ob_ref, object_state.color_type, material_slot);
has_transparent_material = has_transparent_material || mat.is_transparent();
::Image *image = nullptr;
ImageUser *iuser = nullptr;
GPUSamplerState sampler_state = GPUSamplerState::default_sampler();
MaterialTexture texture;
if (object_state.color_type == V3D_SHADING_TEXTURE_COLOR) {
get_material_image(ob_ref.object, material_slot, image, iuser, sampler_state);
texture = MaterialTexture(ob_ref.object, material_slot);
}
draw_mesh(ob_ref, mat, batches[i], handle, image, sampler_state, iuser);
draw_mesh(ob_ref, mat, batches[i], handle, &texture, object_state.show_missing_texture);
}
}
}
@@ -303,12 +303,7 @@ class Instance {
Material mat = get_material(ob_ref, object_state.color_type);
has_transparent_material = has_transparent_material || mat.is_transparent();
draw_mesh(ob_ref,
mat,
batch,
handle,
object_state.image_paint_override,
object_state.override_sampler_state);
draw_mesh(ob_ref, mat, batch, handle, &object_state.image_paint_override);
}
}
@@ -334,14 +329,12 @@ class Instance {
mat.base_color = batch.debug_color();
}
::Image *image = nullptr;
ImageUser *iuser = nullptr;
GPUSamplerState sampler_state = GPUSamplerState::default_sampler();
MaterialTexture texture;
if (object_state.color_type == V3D_SHADING_TEXTURE_COLOR) {
get_material_image(ob_ref.object, batch.material_slot, image, iuser, sampler_state);
texture = MaterialTexture(ob_ref.object, batch.material_slot);
}
draw_mesh(ob_ref, mat, batch.batch, handle, image, sampler_state);
draw_mesh(ob_ref, mat, batch.batch, handle, &texture, object_state.show_missing_texture);
}
}
else {
@@ -351,12 +344,7 @@ class Instance {
mat.base_color = batch.debug_color();
}
draw_mesh(ob_ref,
mat,
batch.batch,
handle,
object_state.image_paint_override,
object_state.override_sampler_state);
draw_mesh(ob_ref, mat, batch.batch, handle, &object_state.image_paint_override);
}
}
}
@@ -388,19 +376,16 @@ class Instance {
ResourceHandle handle = manager.resource_handle(ob_ref.object->object_to_world());
Material mat = get_material(ob_ref, object_state.color_type, psys->part->omat - 1);
::Image *image = nullptr;
ImageUser *iuser = nullptr;
GPUSamplerState sampler_state = GPUSamplerState::default_sampler();
MaterialTexture texture;
if (object_state.color_type == V3D_SHADING_TEXTURE_COLOR) {
get_material_image(ob_ref.object, psys->part->omat - 1, image, iuser, sampler_state);
texture = MaterialTexture(ob_ref.object, psys->part->omat - 1);
}
resources.material_buf.append(mat);
int material_index = resources.material_buf.size() - 1;
draw_to_mesh_pass(ob_ref, mat.is_transparent(), [&](MeshPass &mesh_pass) {
PassMain::Sub &pass = mesh_pass
.get_subpass(eGeometryType::CURVES, image, sampler_state, iuser)
.sub("Hair SubPass");
PassMain::Sub &pass =
mesh_pass.get_subpass(eGeometryType::CURVES, &texture).sub("Hair SubPass");
pass.push_constant("emitter_object_id", int(emitter_handle.raw));
gpu::Batch *batch = hair_sub_pass_setup(pass, scene_state.scene, ob_ref.object, psys, md);
pass.draw(batch, handle, material_index);

View File

@@ -60,52 +60,65 @@ uint32_t Material::pack_data(float metallic, float roughness, float alpha)
return (packed_alpha << 16u) | (packed_roughness << 8u) | packed_metallic;
}
void get_material_image(Object *ob,
int material_slot,
::Image *&image,
ImageUser *&iuser,
GPUSamplerState &sampler_state)
MaterialTexture::MaterialTexture(Object *ob, int material_index)
{
const ::bNode *node = nullptr;
ED_object_get_active_image(ob, material_slot + 1, &image, &iuser, &node, nullptr);
if (node && image) {
switch (node->type) {
case SH_NODE_TEX_IMAGE: {
const NodeTexImage *storage = static_cast<NodeTexImage *>(node->storage);
const bool use_filter = (storage->interpolation != SHD_INTERP_CLOSEST);
sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
switch (storage->extension) {
case SHD_IMAGE_EXTENSION_EXTEND:
default:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_EXTEND;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_EXTEND;
break;
case SHD_IMAGE_EXTENSION_REPEAT:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_REPEAT;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_REPEAT;
break;
case SHD_IMAGE_EXTENSION_MIRROR:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT;
break;
case SHD_IMAGE_EXTENSION_CLIP:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
break;
}
break;
}
case SH_NODE_TEX_ENVIRONMENT: {
const NodeTexEnvironment *storage = static_cast<NodeTexEnvironment *>(node->storage);
const bool use_filter = (storage->interpolation != SHD_INTERP_CLOSEST);
sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
break;
}
default:
BLI_assert_msg(0, "Node type not supported by workbench");
}
::Image *image = nullptr;
ImageUser *user = nullptr;
ED_object_get_active_image(ob, material_index + 1, &image, &user, &node, nullptr);
if (!node || !image) {
return;
}
switch (node->type) {
case SH_NODE_TEX_IMAGE: {
const NodeTexImage *storage = static_cast<NodeTexImage *>(node->storage);
const bool use_filter = (storage->interpolation != SHD_INTERP_CLOSEST);
sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
switch (storage->extension) {
case SHD_IMAGE_EXTENSION_EXTEND:
default:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_EXTEND;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_EXTEND;
break;
case SHD_IMAGE_EXTENSION_REPEAT:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_REPEAT;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_REPEAT;
break;
case SHD_IMAGE_EXTENSION_MIRROR:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT;
break;
case SHD_IMAGE_EXTENSION_CLIP:
sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER;
break;
}
break;
}
case SH_NODE_TEX_ENVIRONMENT: {
const NodeTexEnvironment *storage = static_cast<NodeTexEnvironment *>(node->storage);
const bool use_filter = (storage->interpolation != SHD_INTERP_CLOSEST);
sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
break;
}
default:
BLI_assert_msg(0, "Node type not supported by workbench");
}
gpu = BKE_image_get_gpu_material_texture(image, user, true);
premultiplied = image->alpha_mode == IMA_ALPHA_PREMUL;
alpha_cutoff = !ELEM(image->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED);
name = image->id.name;
}
MaterialTexture::MaterialTexture(::Image *image, ImageUser *user /* = nullptr */)
{
gpu = BKE_image_get_gpu_material_texture(image, user, true);
premultiplied = image->alpha_mode == IMA_ALPHA_PREMUL;
alpha_cutoff = !ELEM(image->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED);
name = image->id.name;
}
} // namespace blender::workbench

View File

@@ -54,43 +54,34 @@ void MeshPass::init_subpasses(ePipelineType pipeline, eLightingType lighting, bo
}
}
PassMain::Sub &MeshPass::get_subpass(
eGeometryType geometry_type,
::Image *image /* = nullptr */,
GPUSamplerState sampler_state /* = GPUSamplerState::default_sampler() */,
ImageUser *iuser /* = nullptr */)
PassMain::Sub &MeshPass::get_subpass(eGeometryType geometry_type,
const MaterialTexture *texture /* = nullptr */)
{
is_empty_ = false;
if (image) {
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 (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, gputex.texture, sampler_state);
}
sub_pass->push_constant("isImageTile", gputex.tile_mapping != nullptr);
sub_pass->push_constant("imagePremult", 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)
* or by reading the Material Clipping Threshold (and set it per material) */
float alpha_cutoff = 0.1f;
if (ELEM(image->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED)) {
alpha_cutoff = -FLT_MAX;
}
sub_pass->push_constant("imageTransparencyCutoff", alpha_cutoff);
return sub_pass;
};
if (texture && texture->gpu.texture) {
auto add_cb = [&] {
PassMain::Sub *sub_pass = passes_[int(geometry_type)][int(eShaderType::TEXTURE)];
sub_pass = &sub_pass->sub(texture->name);
if (texture->gpu.tile_mapping) {
sub_pass->bind_texture(WB_TILE_ARRAY_SLOT, texture->gpu.texture, texture->sampler_state);
sub_pass->bind_texture(WB_TILE_DATA_SLOT, texture->gpu.tile_mapping);
}
else {
sub_pass->bind_texture(WB_TEXTURE_SLOT, texture->gpu.texture, texture->sampler_state);
}
sub_pass->push_constant("isImageTile", texture->gpu.tile_mapping != nullptr);
sub_pass->push_constant("imagePremult", texture->premultiplied);
/* TODO(@pragma37): This setting should be exposed on the user side,
* either as a global parameter (and set it here)
* or by reading the Material Clipping Threshold (and set it per material) */
float alpha_cutoff = texture->alpha_cutoff ? 0.1f : -FLT_MAX;
sub_pass->push_constant("imageTransparencyCutoff", alpha_cutoff);
return sub_pass;
};
return *texture_subpass_map_.lookup_or_add_cb(
TextureSubPassKey(gputex.texture, geometry_type), add_cb);
}
return *texture_subpass_map_.lookup_or_add_cb(
TextureSubPassKey(texture->gpu.texture, geometry_type), add_cb);
}
return *passes_[int(geometry_type)][int(eShaderType::MATERIAL)];

View File

@@ -137,11 +137,7 @@ struct Material {
bool is_transparent();
};
void get_material_image(Object *ob,
int material_index,
::Image *&image,
ImageUser *&iuser,
GPUSamplerState &sampler_state);
ImageGPUTextures get_material_texture(GPUSamplerState &sampler_state);
struct SceneState {
Scene *scene = nullptr;
@@ -186,19 +182,31 @@ struct SceneState {
void init(Object *camera_ob = nullptr);
};
struct ObjectState {
eV3DShadingColorType color_type = V3D_SHADING_SINGLE_COLOR;
bool sculpt_pbvh = false;
::Image *image_paint_override = nullptr;
GPUSamplerState override_sampler_state = GPUSamplerState::default_sampler();
bool draw_shadow = false;
bool use_per_material_batches = false;
struct MaterialTexture {
const char *name = nullptr;
ImageGPUTextures gpu = {};
GPUSamplerState sampler_state = GPUSamplerState::default_sampler();
bool premultiplied = false;
bool alpha_cutoff = false;
ObjectState(const SceneState &scene_state, Object *ob);
MaterialTexture() = default;
MaterialTexture(Object *ob, int material_index);
MaterialTexture(::Image *image, ImageUser *user = nullptr);
};
struct SceneResources;
struct ObjectState {
eV3DShadingColorType color_type = V3D_SHADING_SINGLE_COLOR;
MaterialTexture image_paint_override = {};
bool show_missing_texture = false;
bool draw_shadow = false;
bool use_per_material_batches = false;
bool sculpt_pbvh = false;
ObjectState(const SceneState &scene_state, const SceneResources &resources, Object *ob);
};
class CavityEffect {
private:
/* This value must be kept in sync with the one declared at
@@ -285,6 +293,9 @@ struct SceneResources {
StencilViewWorkaround stencil_view;
Texture missing_tx = "missing_tx";
MaterialTexture missing_texture;
void init(const SceneState &scene_state);
void load_jitter_tx(int total_samples);
};
@@ -309,9 +320,7 @@ class MeshPass : public PassMain {
void init_subpasses(ePipelineType pipeline, eLightingType lighting, bool clip);
PassMain::Sub &get_subpass(eGeometryType geometry_type,
::Image *image = nullptr,
GPUSamplerState sampler_state = GPUSamplerState::default_sampler(),
ImageUser *iuser = nullptr);
const MaterialTexture *texture = nullptr);
};
enum class StencilBits : uint8_t {

View File

@@ -174,6 +174,11 @@ void SceneResources::init(const SceneState &scene_state)
}
clip_planes_buf.push_update();
missing_tx.ensure_2d(
GPU_RGBA8, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, float4(1.0f, 0.0f, 1.0f, 1.0f));
missing_texture.gpu.texture = missing_tx;
missing_texture.name = "Missing Texture";
}
} // namespace blender::workbench

View File

@@ -206,13 +206,13 @@ static const CustomData *get_vert_custom_data(const Mesh *mesh)
return &mesh->vert_data;
}
ObjectState::ObjectState(const SceneState &scene_state, Object *ob)
ObjectState::ObjectState(const SceneState &scene_state,
const SceneResources &resources,
Object *ob)
{
const DRWContextState *draw_ctx = DRW_context_state_get();
const bool is_active = (ob == draw_ctx->obact);
image_paint_override = nullptr;
override_sampler_state = GPUSamplerState::default_sampler();
sculpt_pbvh = BKE_sculptsession_use_pbvh_draw(ob, draw_ctx->rv3d) &&
!DRW_state_is_image_render();
draw_shadow = scene_state.draw_shadows && (ob->dtx & OB_DRAW_NO_SHADOW_CAST) == 0 &&
@@ -267,21 +267,28 @@ ObjectState::ObjectState(const SceneState &scene_state, Object *ob)
}
else if (is_texpaint_mode && has_uv) {
color_type = V3D_SHADING_TEXTURE_COLOR;
show_missing_texture = true;
const ImagePaintSettings *imapaint = &scene_state.scene->toolsettings->imapaint;
if (imapaint->mode == IMAGEPAINT_MODE_IMAGE) {
image_paint_override = imapaint->canvas;
override_sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_REPEAT;
override_sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_REPEAT;
const bool use_linear_filter = imapaint->interp == IMAGEPAINT_INTERP_LINEAR;
override_sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR,
use_linear_filter);
if (imapaint->canvas) {
image_paint_override = MaterialTexture(imapaint->canvas);
image_paint_override.sampler_state.extend_x = GPU_SAMPLER_EXTEND_MODE_REPEAT;
image_paint_override.sampler_state.extend_yz = GPU_SAMPLER_EXTEND_MODE_REPEAT;
const bool use_linear_filter = imapaint->interp == IMAGEPAINT_INTERP_LINEAR;
image_paint_override.sampler_state.set_filtering_flag_from_test(
GPU_SAMPLER_FILTERING_LINEAR, use_linear_filter);
}
else {
image_paint_override = resources.missing_texture;
}
}
}
}
use_per_material_batches = image_paint_override == nullptr && ELEM(color_type,
V3D_SHADING_TEXTURE_COLOR,
V3D_SHADING_MATERIAL_COLOR);
use_per_material_batches = image_paint_override.gpu.texture == nullptr &&
ELEM(color_type,
V3D_SHADING_TEXTURE_COLOR,
V3D_SHADING_MATERIAL_COLOR);
}
} // namespace blender::workbench