mirror of
https://github.com/godotengine/godot.git
synced 2025-01-22 10:32:54 -05:00
Merge pull request #100157 from Zylann/texture_preview_channel_selector
Add color channel filter to editor texture previews
This commit is contained in:
commit
f07e3ed551
13 changed files with 582 additions and 38 deletions
|
@ -1135,7 +1135,7 @@ static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst,
|
|||
}
|
||||
|
||||
bool Image::is_size_po2() const {
|
||||
return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height);
|
||||
return is_power_of_2(width) && is_power_of_2(height);
|
||||
}
|
||||
|
||||
void Image::resize_to_po2(bool p_square, Interpolation p_interpolation) {
|
||||
|
@ -3953,6 +3953,97 @@ String Image::get_format_name(Format p_format) {
|
|||
return format_names[p_format];
|
||||
}
|
||||
|
||||
uint32_t Image::get_format_component_mask(Format p_format) {
|
||||
const uint32_t r = 1;
|
||||
const uint32_t rg = 3;
|
||||
const uint32_t rgb = 7;
|
||||
const uint32_t rgba = 15;
|
||||
|
||||
switch (p_format) {
|
||||
case FORMAT_L8:
|
||||
return rgb;
|
||||
case FORMAT_LA8:
|
||||
return rgba;
|
||||
case FORMAT_R8:
|
||||
return r;
|
||||
case FORMAT_RG8:
|
||||
return rg;
|
||||
case FORMAT_RGB8:
|
||||
return rgb;
|
||||
case FORMAT_RGBA8:
|
||||
return rgba;
|
||||
case FORMAT_RGBA4444:
|
||||
return rgba;
|
||||
case FORMAT_RGB565:
|
||||
return rgb;
|
||||
case FORMAT_RF:
|
||||
return r;
|
||||
case FORMAT_RGF:
|
||||
return rg;
|
||||
case FORMAT_RGBF:
|
||||
return rgb;
|
||||
case FORMAT_RGBAF:
|
||||
return rgba;
|
||||
case FORMAT_RH:
|
||||
return r;
|
||||
case FORMAT_RGH:
|
||||
return rg;
|
||||
case FORMAT_RGBH:
|
||||
return rgb;
|
||||
case FORMAT_RGBAH:
|
||||
return rgba;
|
||||
case FORMAT_RGBE9995:
|
||||
return rgba;
|
||||
case FORMAT_DXT1:
|
||||
return rgb;
|
||||
case FORMAT_DXT3:
|
||||
return rgb;
|
||||
case FORMAT_DXT5:
|
||||
return rgba;
|
||||
case FORMAT_RGTC_R:
|
||||
return r;
|
||||
case FORMAT_RGTC_RG:
|
||||
return rg;
|
||||
case FORMAT_BPTC_RGBA:
|
||||
return rgba;
|
||||
case FORMAT_BPTC_RGBF:
|
||||
return rgb;
|
||||
case FORMAT_BPTC_RGBFU:
|
||||
return rgb;
|
||||
case FORMAT_ETC:
|
||||
return rgb;
|
||||
case FORMAT_ETC2_R11:
|
||||
return r;
|
||||
case FORMAT_ETC2_R11S:
|
||||
return r;
|
||||
case FORMAT_ETC2_RG11:
|
||||
return rg;
|
||||
case FORMAT_ETC2_RG11S:
|
||||
return rg;
|
||||
case FORMAT_ETC2_RGB8:
|
||||
return rgb;
|
||||
case FORMAT_ETC2_RGBA8:
|
||||
return rgba;
|
||||
case FORMAT_ETC2_RGB8A1:
|
||||
return rgba;
|
||||
case FORMAT_ETC2_RA_AS_RG:
|
||||
return rgba;
|
||||
case FORMAT_DXT5_RA_AS_RG:
|
||||
return rgba;
|
||||
case FORMAT_ASTC_4x4:
|
||||
return rgba;
|
||||
case FORMAT_ASTC_4x4_HDR:
|
||||
return rgba;
|
||||
case FORMAT_ASTC_8x8:
|
||||
return rgba;
|
||||
case FORMAT_ASTC_8x8_HDR:
|
||||
return rgba;
|
||||
default:
|
||||
ERR_PRINT("Unhandled format.");
|
||||
return rgba;
|
||||
}
|
||||
}
|
||||
|
||||
Error Image::load_png_from_buffer(const Vector<uint8_t> &p_array) {
|
||||
return _load_from_buffer(p_array, _png_mem_loader_func);
|
||||
}
|
||||
|
|
|
@ -395,6 +395,7 @@ public:
|
|||
Ref<Image> get_region(const Rect2i &p_area) const;
|
||||
|
||||
static String get_format_name(Format p_format);
|
||||
static uint32_t get_format_component_mask(Format p_format);
|
||||
|
||||
Error load_png_from_buffer(const Vector<uint8_t> &p_array);
|
||||
Error load_jpg_from_buffer(const Vector<uint8_t> &p_array);
|
||||
|
|
|
@ -64,10 +64,6 @@ SafeNumeric<uint64_t> Memory::max_usage;
|
|||
|
||||
SafeNumeric<uint64_t> Memory::alloc_count;
|
||||
|
||||
inline bool is_power_of_2(size_t x) {
|
||||
return x && ((x & (x - 1U)) == 0U);
|
||||
}
|
||||
|
||||
void *Memory::alloc_aligned_static(size_t p_bytes, size_t p_alignment) {
|
||||
DEV_ASSERT(is_power_of_2(p_alignment));
|
||||
|
||||
|
|
|
@ -135,6 +135,12 @@ constexpr auto CLAMP(const T m_a, const T2 m_min, const T3 m_max) {
|
|||
|
||||
/* Functions to handle powers of 2 and shifting. */
|
||||
|
||||
// Returns `true` if a positive integer is a power of 2, `false` otherwise.
|
||||
template <typename T>
|
||||
inline bool is_power_of_2(const T x) {
|
||||
return x && ((x & (x - 1)) == 0);
|
||||
}
|
||||
|
||||
// Function to find the next power of 2 to an integer.
|
||||
static _FORCE_INLINE_ unsigned int next_power_of_2(unsigned int x) {
|
||||
if (x == 0) {
|
||||
|
|
1
editor/icons/TexturePreviewChannels.svg
Normal file
1
editor/icons/TexturePreviewChannels.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#000" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m8 1 7 3v8l-7 3-7-3V4Z"/><path fill-opacity=".8" d="M2 5h12v6l-12-.008Z" paint-order="stroke fill markers"/><path fill="#c2c2c2" d="m8 7.5-7 3V12l7 3 7-3v-1.5z"/><path fill="#d6d6d6" d="m8 15-7-3v-1.5l7 3z"/><path fill="#f9f9f9" d="m1 10.5 7 3 7-3-7-3z"/><path fill="#c2c2c2" d="m8 4.25-7 3v1.5l7 3 7-3v-1.5z"/><path fill="#d6d6d6" d="m8 11.75-7-3v-1.5l7 3z"/><path fill="#f9f9f9" d="m1 7.25 7 3 7-3-7-3z"/><path fill="#c2c2c2" d="M8 1 1 4v1.5l7 3 7-3V4Z"/><path fill="#f9f9f9" d="m1 4 7 3 7-3-7-3z"/><path fill="#d6d6d6" d="m8 8.5-7-3V4l7 3z"/></svg>
|
After Width: | Height: | Size: 710 B |
146
editor/plugins/color_channel_selector.cpp
Normal file
146
editor/plugins/color_channel_selector.cpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
/**************************************************************************/
|
||||
/* color_channel_selector.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "color_channel_selector.h"
|
||||
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
|
||||
ColorChannelSelector::ColorChannelSelector() {
|
||||
toggle_button = memnew(Button);
|
||||
toggle_button->set_flat(true);
|
||||
toggle_button->set_toggle_mode(true);
|
||||
toggle_button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_toggled));
|
||||
toggle_button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
|
||||
add_child(toggle_button);
|
||||
|
||||
panel = memnew(PanelContainer);
|
||||
panel->hide();
|
||||
|
||||
HBoxContainer *container = memnew(HBoxContainer);
|
||||
container->add_theme_constant_override("separation", 0);
|
||||
|
||||
create_button(0, "R", container);
|
||||
create_button(1, "G", container);
|
||||
create_button(2, "B", container);
|
||||
create_button(3, "A", container);
|
||||
|
||||
// Use a bit of transparency to be less distracting.
|
||||
set_modulate(Color(1, 1, 1, 0.7));
|
||||
|
||||
panel->add_child(container);
|
||||
|
||||
add_child(panel);
|
||||
}
|
||||
|
||||
void ColorChannelSelector::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_THEME_CHANGED) {
|
||||
// PanelContainer's background is invisible in the editor. We need a background.
|
||||
// And we need this in turn because buttons don't look good without background (for example, hover is transparent).
|
||||
Ref<StyleBox> bg_style = get_theme_stylebox(SceneStringName(panel), "TabContainer");
|
||||
ERR_FAIL_COND(bg_style.is_null());
|
||||
bg_style = bg_style->duplicate();
|
||||
// The default content margin makes the widget become a bit too large. It should be like mini-toolbar.
|
||||
const float editor_scale = EditorScale::get_scale();
|
||||
bg_style->set_content_margin(SIDE_LEFT, 1.0f * editor_scale);
|
||||
bg_style->set_content_margin(SIDE_RIGHT, 1.0f * editor_scale);
|
||||
bg_style->set_content_margin(SIDE_TOP, 1.0f * editor_scale);
|
||||
bg_style->set_content_margin(SIDE_BOTTOM, 1.0f * editor_scale);
|
||||
panel->add_theme_style_override(SceneStringName(panel), bg_style);
|
||||
|
||||
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("TexturePreviewChannels"));
|
||||
toggle_button->set_button_icon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorChannelSelector::set_available_channels_mask(uint32_t p_mask) {
|
||||
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
|
||||
const bool available = (p_mask & (1u << i)) != 0;
|
||||
Button *button = channel_buttons[i];
|
||||
button->set_visible(available);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorChannelSelector::on_channel_button_toggled(bool p_unused_pressed) {
|
||||
emit_signal("selected_channels_changed");
|
||||
}
|
||||
|
||||
uint32_t ColorChannelSelector::get_selected_channels_mask() const {
|
||||
uint32_t mask = 0;
|
||||
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
|
||||
Button *button = channel_buttons[i];
|
||||
if (button->is_visible() && channel_buttons[i]->is_pressed()) {
|
||||
mask |= (1 << i);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Helper
|
||||
Vector4 ColorChannelSelector::get_selected_channel_factors() const {
|
||||
Vector4 channel_factors;
|
||||
const uint32_t mask = get_selected_channels_mask();
|
||||
for (unsigned int i = 0; i < 4; ++i) {
|
||||
if ((mask & (1 << i)) != 0) {
|
||||
channel_factors[i] = 1;
|
||||
}
|
||||
}
|
||||
return channel_factors;
|
||||
}
|
||||
|
||||
void ColorChannelSelector::create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent) {
|
||||
ERR_FAIL_COND(p_channel_index >= CHANNEL_COUNT);
|
||||
ERR_FAIL_COND(channel_buttons[p_channel_index] != nullptr);
|
||||
Button *button = memnew(Button);
|
||||
button->set_text(p_text);
|
||||
button->set_toggle_mode(true);
|
||||
button->set_pressed(true);
|
||||
|
||||
// Don't show focus, it stands out too much and remains visible which can be confusing.
|
||||
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
|
||||
|
||||
// Make it look similar to toolbar buttons.
|
||||
button->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
|
||||
button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_channel_button_toggled));
|
||||
p_parent->add_child(button);
|
||||
channel_buttons[p_channel_index] = button;
|
||||
}
|
||||
|
||||
void ColorChannelSelector::on_toggled(bool p_pressed) {
|
||||
panel->set_visible(p_pressed);
|
||||
}
|
||||
|
||||
void ColorChannelSelector::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("selected_channels_changed"));
|
||||
}
|
65
editor/plugins/color_channel_selector.h
Normal file
65
editor/plugins/color_channel_selector.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**************************************************************************/
|
||||
/* color_channel_selector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef COLOR_CHANNEL_SELECTOR_H
|
||||
#define COLOR_CHANNEL_SELECTOR_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
|
||||
class PanelContainer;
|
||||
class Button;
|
||||
|
||||
class ColorChannelSelector : public HBoxContainer {
|
||||
GDCLASS(ColorChannelSelector, HBoxContainer);
|
||||
|
||||
static const unsigned int CHANNEL_COUNT = 4;
|
||||
|
||||
public:
|
||||
ColorChannelSelector();
|
||||
|
||||
void set_available_channels_mask(uint32_t p_mask);
|
||||
uint32_t get_selected_channels_mask() const;
|
||||
Vector4 get_selected_channel_factors() const;
|
||||
|
||||
private:
|
||||
void _notification(int p_what);
|
||||
|
||||
void on_channel_button_toggled(bool p_unused_pressed);
|
||||
void create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent);
|
||||
void on_toggled(bool p_pressed);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
Button *channel_buttons[CHANNEL_COUNT] = {};
|
||||
PanelContainer *panel = nullptr;
|
||||
Button *toggle_button = nullptr;
|
||||
};
|
||||
|
||||
#endif // COLOR_CHANNEL_SELECTOR_H
|
|
@ -31,6 +31,7 @@
|
|||
#include "texture_3d_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/plugins/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/label.h"
|
||||
|
||||
|
@ -44,8 +45,29 @@ constexpr const char *texture_3d_shader = R"(
|
|||
uniform sampler3D tex;
|
||||
uniform float layer;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
|
@ -94,6 +116,8 @@ void Texture3DEditor::_update_material(bool p_texture_changed) {
|
|||
if (p_texture_changed) {
|
||||
material->set_shader_parameter("tex", texture->get_rid());
|
||||
}
|
||||
|
||||
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
|
||||
}
|
||||
|
||||
void Texture3DEditor::_make_shaders() {
|
||||
|
@ -138,30 +162,44 @@ void Texture3DEditor::_update_gui() {
|
|||
|
||||
layer->set_max(texture->get_depth() - 1);
|
||||
|
||||
const String format = Image::get_format_name(texture->get_format());
|
||||
const Image::Format format = texture->get_format();
|
||||
const String format_name = Image::get_format_name(format);
|
||||
|
||||
if (texture->has_mipmaps()) {
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format());
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_depth();
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_depth();
|
||||
|
||||
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_depth(),
|
||||
format,
|
||||
format_name,
|
||||
mip_count,
|
||||
String::humanize_size(memory)));
|
||||
|
||||
} else {
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_depth();
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_depth();
|
||||
|
||||
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_depth(),
|
||||
format,
|
||||
format_name,
|
||||
String::humanize_size(memory)));
|
||||
}
|
||||
|
||||
const uint32_t components_mask = Image::get_format_component_mask(format);
|
||||
if (is_power_of_2(components_mask)) {
|
||||
// Only one channel available, no point in showing a channel selector.
|
||||
channel_selector->hide();
|
||||
} else {
|
||||
channel_selector->show();
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void Texture3DEditor::on_selected_channels_changed() {
|
||||
_update_material(false);
|
||||
}
|
||||
|
||||
void Texture3DEditor::edit(Ref<Texture3D> p_texture) {
|
||||
|
@ -215,6 +253,11 @@ Texture3DEditor::Texture3DEditor() {
|
|||
|
||||
add_child(layer);
|
||||
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &Texture3DEditor::on_selected_channels_changed));
|
||||
channel_selector->set_anchors_preset(Control::PRESET_TOP_LEFT);
|
||||
add_child(channel_selector);
|
||||
|
||||
info = memnew(Label);
|
||||
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include "scene/resources/shader.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class ColorChannelSelector;
|
||||
|
||||
class Texture3DEditor : public Control {
|
||||
GDCLASS(Texture3DEditor, Control);
|
||||
|
||||
|
@ -49,6 +51,8 @@ class Texture3DEditor : public Control {
|
|||
|
||||
Control *texture_rect = nullptr;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
bool setting = false;
|
||||
|
||||
void _make_shaders();
|
||||
|
@ -67,6 +71,8 @@ class Texture3DEditor : public Control {
|
|||
void _update_material(bool p_texture_changed);
|
||||
void _update_gui();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "texture_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/plugins/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/aspect_ratio_container.h"
|
||||
#include "scene/gui/color_rect.h"
|
||||
|
@ -41,6 +42,36 @@
|
|||
#include "scene/resources/compressed_texture.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/portable_compressed_texture.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
|
||||
constexpr const char *texture_2d_shader = R"(
|
||||
shader_type canvas_item;
|
||||
render_mode blend_mix;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = filter_preview_colors(texture(TEXTURE, UV), u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
TextureRect *TexturePreview::get_texture_display() {
|
||||
return texture_display;
|
||||
|
@ -72,8 +103,8 @@ void TexturePreview::_notification(int p_what) {
|
|||
|
||||
void TexturePreview::_draw_outline() {
|
||||
const float outline_width = Math::round(EDSCALE);
|
||||
const Rect2 outline_rect = Rect2(Vector2(), texture_display->get_size()).grow(outline_width * 0.5);
|
||||
texture_display->draw_rect(outline_rect, cached_outline_color, false, outline_width);
|
||||
const Rect2 outline_rect = Rect2(Vector2(), outline_overlay->get_size()).grow(outline_width * 0.5);
|
||||
outline_overlay->draw_rect(outline_rect, cached_outline_color, false, outline_width);
|
||||
}
|
||||
|
||||
void TexturePreview::_update_texture_display_ratio() {
|
||||
|
@ -82,25 +113,49 @@ void TexturePreview::_update_texture_display_ratio() {
|
|||
}
|
||||
}
|
||||
|
||||
void TexturePreview::_update_metadata_label_text() {
|
||||
const Ref<Texture2D> texture = texture_display->get_texture();
|
||||
|
||||
String format;
|
||||
if (Object::cast_to<ImageTexture>(*texture)) {
|
||||
format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format());
|
||||
} else if (Object::cast_to<CompressedTexture2D>(*texture)) {
|
||||
format = Image::get_format_name(Object::cast_to<CompressedTexture2D>(*texture)->get_format());
|
||||
} else {
|
||||
format = texture->get_class();
|
||||
static Image::Format get_texture_2d_format(const Ref<Texture2D> &p_texture) {
|
||||
const Ref<ImageTexture> image_texture = p_texture;
|
||||
if (image_texture.is_valid()) {
|
||||
return image_texture->get_format();
|
||||
}
|
||||
|
||||
const Ref<Image> image = texture->get_image();
|
||||
const Ref<CompressedTexture2D> compressed_texture = p_texture;
|
||||
if (compressed_texture.is_valid()) {
|
||||
return compressed_texture->get_format();
|
||||
}
|
||||
|
||||
// AtlasTexture?
|
||||
|
||||
// Unknown
|
||||
return Image::FORMAT_MAX;
|
||||
}
|
||||
|
||||
static int get_texture_mipmaps_count(const Ref<Texture2D> &p_texture) {
|
||||
ERR_FAIL_COND_V(p_texture.is_null(), -1);
|
||||
// We are having to download the image only to get its mipmaps count. It would be nice if we didn't have to.
|
||||
Ref<Image> image = p_texture->get_image();
|
||||
if (image.is_valid()) {
|
||||
const int mipmaps = image->get_mipmap_count();
|
||||
return image->get_mipmap_count();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TexturePreview::_update_metadata_label_text() {
|
||||
const Ref<Texture2D> texture = texture_display->get_texture();
|
||||
ERR_FAIL_COND(texture.is_null());
|
||||
|
||||
const Image::Format format = get_texture_2d_format(texture.ptr());
|
||||
|
||||
const String format_name = format != Image::FORMAT_MAX ? Image::get_format_name(format) : texture->get_class();
|
||||
|
||||
const Vector2i resolution = texture->get_size();
|
||||
const int mipmaps = get_texture_mipmaps_count(texture);
|
||||
|
||||
if (format != Image::FORMAT_MAX) {
|
||||
// Avoid signed integer overflow that could occur with huge texture sizes by casting everything to uint64_t.
|
||||
uint64_t memory = uint64_t(image->get_width()) * uint64_t(image->get_height()) * uint64_t(Image::get_format_pixel_size(image->get_format()));
|
||||
uint64_t memory = uint64_t(resolution.x) * uint64_t(resolution.y) * uint64_t(Image::get_format_pixel_size(format));
|
||||
// Handle VRAM-compressed formats that are stored with 4 bpp.
|
||||
memory >>= Image::get_format_pixel_rshift(image->get_format());
|
||||
memory >>= Image::get_format_pixel_rshift(format);
|
||||
|
||||
float mipmaps_multiplier = 1.0;
|
||||
float mipmap_increase = 0.25;
|
||||
|
@ -117,7 +172,7 @@ void TexturePreview::_update_metadata_label_text() {
|
|||
vformat(String::utf8("%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format,
|
||||
format_name,
|
||||
mipmaps,
|
||||
String::humanize_size(memory)));
|
||||
} else {
|
||||
|
@ -127,7 +182,7 @@ void TexturePreview::_update_metadata_label_text() {
|
|||
vformat(String::utf8("%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format,
|
||||
format_name,
|
||||
String::humanize_size(memory)));
|
||||
}
|
||||
} else {
|
||||
|
@ -135,10 +190,14 @@ void TexturePreview::_update_metadata_label_text() {
|
|||
vformat(String::utf8("%d×%d %s"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format));
|
||||
format_name));
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePreview::on_selected_channels_changed() {
|
||||
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
|
||||
}
|
||||
|
||||
TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
|
||||
set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE);
|
||||
|
||||
|
@ -163,19 +222,48 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
|
|||
checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED);
|
||||
centering_container->add_child(checkerboard);
|
||||
|
||||
{
|
||||
Ref<Shader> shader;
|
||||
shader.instantiate();
|
||||
shader->set_code(texture_2d_shader);
|
||||
|
||||
material.instantiate();
|
||||
material->set_shader(shader);
|
||||
material->set_shader_parameter("u_channel_factors", Vector4(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
texture_display = memnew(TextureRect);
|
||||
texture_display->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
|
||||
texture_display->set_texture(p_texture);
|
||||
texture_display->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
texture_display->set_material(material);
|
||||
centering_container->add_child(texture_display);
|
||||
|
||||
texture_display->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline));
|
||||
// Creating a separate control so it is not affected by the filtering shader.
|
||||
outline_overlay = memnew(Control);
|
||||
centering_container->add_child(outline_overlay);
|
||||
|
||||
outline_overlay->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline));
|
||||
|
||||
if (p_texture.is_valid()) {
|
||||
_update_texture_display_ratio();
|
||||
p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_texture_display_ratio));
|
||||
}
|
||||
|
||||
// Null can be passed by `Camera3DPreview` (which immediately after sets a texture anyways).
|
||||
const Image::Format format = p_texture.is_valid() ? get_texture_2d_format(p_texture.ptr()) : Image::FORMAT_MAX;
|
||||
const uint32_t components_mask = format != Image::FORMAT_MAX ? Image::get_format_component_mask(format) : 0xf;
|
||||
|
||||
// Add color channel selector at the bottom left if more than 1 channel is available.
|
||||
if (p_show_metadata && !is_power_of_2(components_mask)) {
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &TexturePreview::on_selected_channels_changed));
|
||||
channel_selector->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
|
||||
channel_selector->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
add_child(channel_selector);
|
||||
}
|
||||
|
||||
if (p_show_metadata) {
|
||||
metadata_label = memnew(Label);
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
class AspectRatioContainer;
|
||||
class ColorRect;
|
||||
class TextureRect;
|
||||
class ShaderMaterial;
|
||||
class ColorChannelSelector;
|
||||
|
||||
class TexturePreview : public MarginContainer {
|
||||
GDCLASS(TexturePreview, MarginContainer);
|
||||
|
@ -47,10 +49,14 @@ private:
|
|||
TextureRect *texture_display = nullptr;
|
||||
|
||||
MarginContainer *margin_container = nullptr;
|
||||
Control *outline_overlay = nullptr;
|
||||
AspectRatioContainer *centering_container = nullptr;
|
||||
ColorRect *bg_rect = nullptr;
|
||||
TextureRect *checkerboard = nullptr;
|
||||
Label *metadata_label = nullptr;
|
||||
Ref<ShaderMaterial> material;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
Color cached_outline_color;
|
||||
|
||||
|
@ -61,6 +67,8 @@ protected:
|
|||
void _notification(int p_what);
|
||||
void _update_texture_display_ratio();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
public:
|
||||
TextureRect *get_texture_display();
|
||||
TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "texture_layered_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/plugins/color_channel_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/label.h"
|
||||
|
||||
|
@ -43,9 +44,29 @@ constexpr const char *array_2d_shader = R"(
|
|||
|
||||
uniform sampler2DArray tex;
|
||||
uniform float layer;
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
|
@ -58,9 +79,30 @@ constexpr const char *cubemap_shader = R"(
|
|||
uniform vec3 normal;
|
||||
uniform mat3 rot;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
|
||||
COLOR = textureLod(tex, n, 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
|
@ -73,9 +115,30 @@ constexpr const char *cubemap_array_shader = R"(
|
|||
uniform mat3 rot;
|
||||
uniform float layer;
|
||||
|
||||
uniform vec4 u_channel_factors = vec4(1.0);
|
||||
|
||||
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
|
||||
// Filter RGB.
|
||||
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
|
||||
|
||||
// Remove transparency when alpha is not enabled.
|
||||
output_color.a = mix(1.0, output_color.a, factors.a);
|
||||
|
||||
// Switch to opaque grayscale when visualizing only one channel.
|
||||
float csum = factors.r + factors.g + factors.b + factors.a;
|
||||
float single = clamp(2.0 - csum, 0.0, 1.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float c = input_color[i];
|
||||
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
|
||||
COLOR = textureLod(tex, vec4(n, layer), 0.0);
|
||||
COLOR = filter_preview_colors(COLOR, u_channel_factors);
|
||||
}
|
||||
)";
|
||||
|
||||
|
@ -102,7 +165,8 @@ void TextureLayeredEditor::_update_gui() {
|
|||
|
||||
_texture_rect_update_area();
|
||||
|
||||
const String format = Image::get_format_name(texture->get_format());
|
||||
const Image::Format format = texture->get_format();
|
||||
const String format_name = Image::get_format_name(format);
|
||||
String texture_info;
|
||||
|
||||
switch (texture->get_layered_type()) {
|
||||
|
@ -113,7 +177,7 @@ void TextureLayeredEditor::_update_gui() {
|
|||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_layers(),
|
||||
format);
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
case TextureLayered::LAYERED_TYPE_CUBEMAP: {
|
||||
|
@ -122,7 +186,7 @@ void TextureLayeredEditor::_update_gui() {
|
|||
texture_info = vformat(String::utf8("%d×%d %s\n"),
|
||||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
format);
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
case TextureLayered::LAYERED_TYPE_CUBEMAP_ARRAY: {
|
||||
|
@ -132,7 +196,7 @@ void TextureLayeredEditor::_update_gui() {
|
|||
texture->get_width(),
|
||||
texture->get_height(),
|
||||
texture->get_layers() / 6,
|
||||
format);
|
||||
format_name);
|
||||
|
||||
} break;
|
||||
|
||||
|
@ -141,21 +205,30 @@ void TextureLayeredEditor::_update_gui() {
|
|||
}
|
||||
|
||||
if (texture->has_mipmaps()) {
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format());
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_layers();
|
||||
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_layers();
|
||||
|
||||
texture_info += vformat(TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
mip_count,
|
||||
String::humanize_size(memory));
|
||||
|
||||
} else {
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_layers();
|
||||
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_layers();
|
||||
|
||||
texture_info += vformat(TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
|
||||
String::humanize_size(memory));
|
||||
}
|
||||
|
||||
info->set_text(texture_info);
|
||||
|
||||
const uint32_t components_mask = Image::get_format_component_mask(format);
|
||||
if (is_power_of_2(components_mask)) {
|
||||
// Only one channel available, no point in showing a channel selector.
|
||||
channel_selector->hide();
|
||||
} else {
|
||||
channel_selector->show();
|
||||
channel_selector->set_available_channels_mask(components_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_notification(int p_what) {
|
||||
|
@ -212,6 +285,15 @@ void TextureLayeredEditor::_update_material(bool p_texture_changed) {
|
|||
if (p_texture_changed) {
|
||||
materials[texture->get_layered_type()]->set_shader_parameter("tex", texture->get_rid());
|
||||
}
|
||||
|
||||
const Vector4 channel_factors = channel_selector->get_selected_channel_factors();
|
||||
for (unsigned int i = 0; i < 3; ++i) {
|
||||
materials[i]->set_shader_parameter("u_channel_factors", channel_factors);
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::on_selected_channels_changed() {
|
||||
_update_material(false);
|
||||
}
|
||||
|
||||
void TextureLayeredEditor::_make_shaders() {
|
||||
|
@ -309,6 +391,11 @@ TextureLayeredEditor::TextureLayeredEditor() {
|
|||
|
||||
add_child(layer);
|
||||
|
||||
channel_selector = memnew(ColorChannelSelector);
|
||||
channel_selector->connect("selected_channels_changed", callable_mp(this, &TextureLayeredEditor::on_selected_channels_changed));
|
||||
channel_selector->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
|
||||
add_child(channel_selector);
|
||||
|
||||
info = memnew(Label);
|
||||
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include "scene/resources/shader.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class ColorChannelSelector;
|
||||
|
||||
class TextureLayeredEditor : public Control {
|
||||
GDCLASS(TextureLayeredEditor, Control);
|
||||
|
||||
|
@ -53,6 +55,8 @@ class TextureLayeredEditor : public Control {
|
|||
|
||||
bool setting = false;
|
||||
|
||||
ColorChannelSelector *channel_selector = nullptr;
|
||||
|
||||
void _make_shaders();
|
||||
void _update_material(bool p_texture_changed);
|
||||
|
||||
|
@ -69,6 +73,8 @@ class TextureLayeredEditor : public Control {
|
|||
|
||||
void _update_gui();
|
||||
|
||||
void on_selected_channels_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
|
Loading…
Reference in a new issue