Fix node thumbnails not converging with progressive renderers; add colorSpace and Sdr-enum widgets
- NodeThumbnailCache/MaterialPreviewRenderer: keep a material node thumbnail stale (re-pumped each frame) until the shared preview renderer actually converges, instead of caching after one render pass. Progressive delegates (Arnold/Cycles/Embree) need multiple passes to accumulate past their first noisy sample, so thumbnails previously froze on an early frame and only "fixed themselves" when toggling thumbnail display forced a fresh render. - MaterialEditorPanel: add a Color Space row under Asset-typed shader inputs, authoring the attribute's colorSpace metadata from the active OCIO config's color spaces. - MaterialManager: surface Sdr-declared allowed values (enums) per shader input so the property panel can render a dropdown instead of free text. - CMakePresets.json: build the release preset with debug info (RelWithDebInfo) for easier diagnosis. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "../core/commands/RenamePrimCommand.h"
|
||||
#include "../utils/Logger.h"
|
||||
#include "../utils/FileDialog.h"
|
||||
#include "../utils/OcioConfigParser.h"
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
#include <pxr/usd/usdShade/material.h>
|
||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||
@@ -365,6 +366,14 @@ void MaterialEditorPanel::SetColorCorrectionFromPrefs(int ccMode, const std::str
|
||||
const std::string& ocioColorSpace,
|
||||
const std::string& ocioLook) {
|
||||
m_preview.SetColorCorrection(ccMode, ocioDisplay, ocioView, ocioColorSpace, ocioLook);
|
||||
// Cached for the per-frame NodeThumbnailCache::SyncRenderSettings() call
|
||||
// in RenderNodeGraphCanvas, which keeps in-canvas node thumbnails
|
||||
// matching this same color correction (see its declaration).
|
||||
m_ccMode = ccMode;
|
||||
m_ocioDisplay = ocioDisplay;
|
||||
m_ocioView = ocioView;
|
||||
m_ocioColorSpace = ocioColorSpace;
|
||||
m_ocioLook = ocioLook;
|
||||
}
|
||||
|
||||
void MaterialEditorPanel::Render() {
|
||||
@@ -570,6 +579,18 @@ void MaterialEditorPanel::RenderSelectedNodeProperties() {
|
||||
RenderInputValueWidget(*node, input, prim);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
if (input.typeName == pxr::SdfValueTypeNames->Asset) {
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Color Space");
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::PushItemWidth(-FLT_MIN);
|
||||
RenderColorSpaceWidget(*node, input, prim);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
@@ -645,11 +666,39 @@ void MaterialEditorPanel::RenderInputValueWidget(const ShaderGraphNode& node,
|
||||
std::string s;
|
||||
if (current.IsHolding<std::string>()) s = current.UncheckedGet<std::string>();
|
||||
else if (current.IsHolding<pxr::TfToken>()) s = current.UncheckedGet<pxr::TfToken>().GetString();
|
||||
char buf[512];
|
||||
std::snprintf(buf, sizeof(buf), "%s", s.c_str());
|
||||
if (ImGui::InputText(widgetId.c_str(), buf, sizeof(buf), ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (type == tn->String) newValue = std::string(buf);
|
||||
else newValue = pxr::TfToken(buf);
|
||||
|
||||
if (!input.allowedValues.empty()) {
|
||||
// Sdr-declared enum (e.g. UsdUVTexture's sourceColorSpace:
|
||||
// raw/sRGB/auto) — a dropdown of exactly the legal values instead
|
||||
// of free text, which could author anything the renderer ignores.
|
||||
// A selection is one atomic edit (no drag-in-progress concept for
|
||||
// a combo), so it's committed inline here — like the Asset browse
|
||||
// button below — instead of through the drag/type commit
|
||||
// machinery at the end of this function.
|
||||
if (ImGui::BeginCombo(widgetId.c_str(), s.c_str())) {
|
||||
for (const std::string& option : input.allowedValues) {
|
||||
bool selected = (option == s);
|
||||
if (ImGui::Selectable(option.c_str(), selected) && !selected) {
|
||||
m_preEditValue = current;
|
||||
m_preEditWasAuthored = authored;
|
||||
pxr::UsdShadeShader shader(prim);
|
||||
if (shader)
|
||||
shader.CreateInput(pxr::TfToken(input.name), input.typeName)
|
||||
.Set(type == tn->String ? pxr::VtValue(option) : pxr::VtValue(pxr::TfToken(option)));
|
||||
CommitInputEdit(node, input);
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
char buf[512];
|
||||
std::snprintf(buf, sizeof(buf), "%s", s.c_str());
|
||||
if (ImGui::InputText(widgetId.c_str(), buf, sizeof(buf), ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (type == tn->String) newValue = std::string(buf);
|
||||
else newValue = pxr::TfToken(buf);
|
||||
}
|
||||
}
|
||||
} else if (type == tn->Asset) {
|
||||
std::string s;
|
||||
@@ -715,6 +764,64 @@ void MaterialEditorPanel::RenderInputValueWidget(const ShaderGraphNode& node,
|
||||
CommitInputEdit(node, input);
|
||||
}
|
||||
|
||||
void MaterialEditorPanel::RenderColorSpaceWidget(const ShaderGraphNode& node, const ShaderPinInfo& input,
|
||||
const pxr::UsdPrim& prim) {
|
||||
if (!m_stage || !m_commandHistory) return;
|
||||
pxr::UsdAttribute attr = prim.GetAttribute(pxr::TfToken("inputs:" + input.name));
|
||||
if (!attr) return;
|
||||
|
||||
const std::string current = attr.HasColorSpace() ? attr.GetColorSpace().GetString() : std::string();
|
||||
|
||||
// Listed from the active OCIO config ($OCIO — the same one driving
|
||||
// viewport/preview color correction) rather than a fixed raw/sRGB/auto
|
||||
// set: those are only meaningful to UsdUVTexture's own sourceColorSpace
|
||||
// input, while this authors the renderer-agnostic colorSpace attribute
|
||||
// metadata, so the choices should span whatever color spaces are
|
||||
// actually defined and thus consistently interpretable across every
|
||||
// render delegate (Storm, Arnold, Cycles, ...) rather than being tied to
|
||||
// one shading backend's built-in vocabulary.
|
||||
const OcioConfig& ocfg = GetCurrentOcioConfig();
|
||||
static const std::vector<std::string> kFallback = {"raw", "sRGB"};
|
||||
const std::vector<std::string>& colorSpaces = ocfg.valid && !ocfg.colorSpaces.empty()
|
||||
? ocfg.colorSpaces : kFallback;
|
||||
|
||||
auto commit = [&](const std::string& newValue) {
|
||||
pxr::UsdStageRefPtr stage = m_stage;
|
||||
pxr::SdfPath nodePath = node.path;
|
||||
pxr::TfToken attrName = attr.GetName();
|
||||
std::string oldValue = current;
|
||||
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
|
||||
"Set color space on " + node.path.GetName() + "." + input.name,
|
||||
[stage, nodePath, attrName, newValue]() {
|
||||
pxr::UsdAttribute a = stage->GetPrimAtPath(nodePath).GetAttribute(attrName);
|
||||
if (!a) return;
|
||||
if (newValue.empty()) a.ClearColorSpace();
|
||||
else a.SetColorSpace(pxr::TfToken(newValue));
|
||||
},
|
||||
[stage, nodePath, attrName, oldValue]() {
|
||||
pxr::UsdAttribute a = stage->GetPrimAtPath(nodePath).GetAttribute(attrName);
|
||||
if (!a) return;
|
||||
if (oldValue.empty()) a.ClearColorSpace();
|
||||
else a.SetColorSpace(pxr::TfToken(oldValue));
|
||||
}));
|
||||
};
|
||||
|
||||
if (ImGui::BeginCombo("##colorSpace", current.empty() ? "(unset)" : current.c_str())) {
|
||||
if (ImGui::Selectable("(unset)", current.empty()) && !current.empty())
|
||||
commit(std::string());
|
||||
if (current.empty()) ImGui::SetItemDefaultFocus();
|
||||
for (const std::string& cs : colorSpaces) {
|
||||
const bool selected = (cs == current);
|
||||
if (ImGui::Selectable(cs.c_str(), selected) && !selected)
|
||||
commit(cs);
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::IsItemHovered() && ocfg.valid)
|
||||
ImGui::SetTooltip("Color spaces from the active OCIO config");
|
||||
}
|
||||
|
||||
void MaterialEditorPanel::CommitInputEdit(const ShaderGraphNode& node, const ShaderPinInfo& input) {
|
||||
if (!m_stage || !m_commandHistory) return;
|
||||
|
||||
@@ -1418,6 +1525,13 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
|
||||
const ImVec2 viewCenterScreen(regionPos.x + regionSize.x * 0.5f,
|
||||
regionPos.y + regionSize.y * 0.5f);
|
||||
|
||||
// Keep node thumbnails' renderer/color-correction matching the big
|
||||
// preview swatch (cheap no-op unless something actually changed) before
|
||||
// pumping, so a change this frame invalidates stale entries in time to
|
||||
// be picked up by the pump below.
|
||||
m_thumbnails.SyncRenderSettings(m_preview.GetRendererPlugin(), m_ccMode,
|
||||
m_ocioDisplay, m_ocioView, m_ocioColorSpace, m_ocioLook);
|
||||
|
||||
// Drain finished texture decodes (GL upload), render one stale material
|
||||
// thumbnail, and prune dead entries — GL/FBO work kept outside the
|
||||
// node-editor's Begin/End so it never touches mid-draw ImGui state.
|
||||
|
||||
Reference in New Issue
Block a user