USD: Improved Texture Coordinate Translation (UsdTransform2d) #114821

Merged
Michael Kowalski merged 7 commits from CharlesWardlaw/blender:feature/port_mapping_to_main into main 2023-11-27 17:18:56 +01:00

UsdTransform2d was previously ignored, and the material read would stop when it found these nodes. This resulted in many surfaces having their textures mapped incorrectly when imported into Blender, and worse, resulted in the loss of that data when round-tripping back out of Blender.

This patch (mostly the work of @makowalski) adds support for the Transform 2D node and also ensures that the wrap modes are similarly written correctly.

Attached are files demonstrating the differences. On import before the patch:

image

Shader graph after the patch:

image

Exports are similarly handled.

UsdTransform2d was previously ignored, and the material read would stop when it found these nodes. This resulted in many surfaces having their textures mapped incorrectly when imported into Blender, and worse, resulted in the loss of that data when round-tripping back out of Blender. This patch (mostly the work of @makowalski) adds support for the Transform 2D node and also ensures that the wrap modes are similarly written correctly. Attached are files demonstrating the differences. On import before the patch: ![image](/attachments/47197a13-aab4-4ffe-8f75-151bc59e8722) Shader graph after the patch: ![image](/attachments/b8371b55-97da-4ce3-8460-7945ab3e3b50) Exports are similarly handled.
Charles Wardlaw added 3 commits 2023-11-14 00:18:54 +01:00
Charles Wardlaw requested review from Michael Kowalski 2023-11-14 00:19:04 +01:00
Charles Wardlaw requested review from Hans Goudey 2023-11-14 00:19:13 +01:00
Jesse Yurkovich requested changes 2023-11-14 06:14:08 +01:00
@ -64,0 +69,4 @@
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);

These wrap mode values don't seem to be used in the reader.

These wrap mode values don't seem to be used in the reader.
Author
Member

Good catch-- on the port I missed a function. Thanks!

Good catch-- on the port I missed a function. Thanks!
CharlesWardlaw marked this conversation as resolved
@ -329,0 +367,4 @@
{
bNodeSocket *sock = nodeFindSocket(node, SOCK_IN, identifier);
if (!sock) {
WM_reportf(RPT_ERROR,

We shouldn't use WM_reportf any longer. Such reports were changed as part of b684864561 last month. All calls to WM_reportf in this PR will have to change.

We shouldn't use `WM_reportf` any longer. Such reports were changed as part of b6848645614 last month. All calls to WM_reportf in this PR will have to change.

In addition, these kind of 'refined' detailed messages are better suited for CLOG output than BKE_report.

Reports should be considered as high-level information that is important to be printed to the user in the UI. More technical details should rather use the logging system, to avoid spamming users with hundreds of messages that they will not read (or even actually see).

This is also something that needs to be addresses in the whole USD IO area of Blender, but would be good to get it for new code at least. :)

In addition, these kind of 'refined' detailed messages are better suited for `CLOG` output than `BKE_report`. Reports should be considered as high-level information that is important to be printed to the user in the UI. More technical details should rather use the logging system, to avoid spamming users with hundreds of messages that they will not read (or even actually see). This is also something that needs to be addresses in the whole USD IO area of Blender, but would be good to get it for new code at least. :)
Author
Member

I'm happy to make any changes you like here, but the new BKE_report thing introduces many intrusive changes that are in my opinion unnecessary.

  • We're now passing a ReportList pointer through a LOT of functions-- is this the best solution? Was a central logging store considered, where BKE_reportf's first parameter would be an std::string log name or some sort of integer handle that looks up the ReportList from a central place instead? This seems to be how most logging systems work, and wouldn't preclude doing a pre-write lock on a per-log basis to get around threading issues.

  • If that's not a possibility, why are we passing a pointer as opposed to passing a ReportList structure or class by reference?

@mont29 I'd appreciate a more specific idea on CLOG vs BKE_report-- When we've spammed users in the past, we've corrected the issues to reduce spam by fixing or adding features. I'd rather have people annoyed by printouts than have them unaware of the reason why a file imported incorrectly, but I will defer to your guidance.

I'm happy to make any changes you like here, but the new BKE_report thing introduces many intrusive changes that are in my opinion unnecessary. - We're now passing a ReportList pointer through a LOT of functions-- is this the best solution? Was a central logging store considered, where BKE_reportf's first parameter would be an std::string log name or some sort of integer handle that looks up the ReportList from a central place instead? This seems to be how most logging systems work, and wouldn't preclude doing a pre-write lock on a per-log basis to get around threading issues. - If that's not a possibility, why are we passing a pointer as opposed to passing a ReportList structure or class by reference? @mont29 I'd appreciate a more specific idea on CLOG vs BKE_report-- When we've spammed users in the past, we've corrected the issues to reduce spam by fixing or adding features. I'd rather have people annoyed by printouts than have them unaware of the reason why a file imported incorrectly, but I will defer to your guidance.
CharlesWardlaw marked this conversation as resolved
@ -703,0 +792,4 @@
if (pxr::UsdShadeInput scale_input = get_input(usd_shader, usdtokens::scale)) {
pxr::VtValue val;
if (scale_input.Get(&val) && val.CanCast<pxr::GfVec2f>()) {
pxr::GfVec2f scale_val = val.Cast<pxr::GfVec2f>().UncheckedGet<pxr::GfVec2f>();

As someone new to the USD API, I've noticed there's like 5 different coding conventions when it comes to getting values:

if (Attr.Get(val) && val.IsHolding<T>()) { ... val.Get<T>() }
if (Attr.Get(val) && val.IsHolding<T>()) { ... val.UncheckedGet<T>() }
if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().Get<T>() }
if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().UncheckedGet<T>() }
if (Attr.Get(val)) { ... val.Get<T>() }

Is it possible to choose just 1 style? In what situations are the Casts necessary? Can UncheckedGet always be used since we verify in some form first?

As someone new to the USD API, I've noticed there's like 5 different coding conventions when it comes to getting values: ``` if (Attr.Get(val) && val.IsHolding<T>()) { ... val.Get<T>() } if (Attr.Get(val) && val.IsHolding<T>()) { ... val.UncheckedGet<T>() } if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().Get<T>() } if (Attr.Get(val) && val.CanCast<T>()) { ... val.Cast<T>().UncheckedGet<T>() } if (Attr.Get(val)) { ... val.Get<T>() } ``` Is it possible to choose just 1 style? In what situations are the Casts necessary? Can `UncheckedGet` always be used since we verify in some form first?
Author
Member

God, casts are necessary all over the place. >_<;

One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast.

I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.

God, casts are necessary all over the place. >_<; One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast. I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.

God, casts are necessary all over the place. >_<;

One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast.

I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this.

I agree casts are often necessary, because in practice one finds that attributes are not always consistently authored, or for backward compatibility when the USD specification has changed. That said, we can perhaps open a separate task to review the existing USD import code and decide on a more consistent convention throughout.

> God, casts are necessary all over the place. >_<; > > One example is something like GfVec3f -- that could also be stored as GfVec3d, GfVec3i, or GfVec3h. Going from float to doubles makes sense, but the other two are less useful / used and don't convert automatically. So, if you want a GfVec3f out but the value is stored as one of the others, you need to cast. > > I don't know about one style over the others; I feel like sometimes it works one way, and sometimes it works another way. I'll defer to @makowalski on this. I agree casts are often necessary, because in practice one finds that attributes are not always consistently authored, or for backward compatibility when the USD specification has changed. That said, we can perhaps open a separate task to review the existing USD import code and decide on a more consistent convention throughout.
CharlesWardlaw marked this conversation as resolved
@ -77,0 +80,4 @@
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
static const pxr::TfToken emissiveColor("emissiveColor", pxr::TfToken::Immortal);

There is another "emissiveColor" token name defined above already.

There is another "emissiveColor" token name defined above already.
CharlesWardlaw marked this conversation as resolved
@ -82,0 +98,4 @@
static const pxr::TfToken attribute("attribute", pxr::TfToken::Immortal);
static const pxr::TfToken bsdf("bsdf", pxr::TfToken::Immortal);
static const pxr::TfToken closure("closure", pxr::TfToken::Immortal);
static const pxr::TfToken vector("vector", pxr::TfToken::Immortal);

These newly added usdtokens don't seem to be used either? Everything from cycles down to vector.

These newly added usdtokens don't seem to be used either? Everything from cycles down to vector.
Author
Member

UVMap is used, but I removed the rest.

UVMap is used, but I removed the rest.
CharlesWardlaw marked this conversation as resolved
@ -166,2 +206,2 @@
if (!input_link &&
((bNodeSocketValueFloat *)emission_strength_sock->default_value)->value == 0.0f) {
if (!emission_strength_sock) {

This seems like another case where we'd, maybe, want to know we didn't find the socket? In general not being able to find a socket for one of our own nodes is totally an error on our part, nothing the user can do about it, but we don't have a "Retail assert" right now so tracing is the only option... I'm torn between a) silently continuing, b) tracing and continuing, or c) just use a Debug assert for folks running debug and crash with null-ref in retail.

This seems like another case where we'd, maybe, want to know we didn't find the socket? In general not being able to find a socket for one of our own nodes is totally an error on our part, nothing the user can do about it, but we don't have a "Retail assert" right now so tracing is the only option... I'm torn between a) silently continuing, b) tracing and continuing, or c) just use a Debug assert for folks running debug and crash with null-ref in retail.
Author
Member

This kind of missing socket stuff occurs either with broken .blend files or, sometimes, when loading stuff between versions and upgrades or downgrades don't work 100%. I don't think normal users who stick to official releases will hit something like this.

The version of Blender we publish through our launcher, however, is constantly running on top of main. We at times ingest bugs that are the result of refactors or other upstream changes, but are not always able to ingest the subsequent fixes as quickly.

I am 200% against allowing the application to get into a crash state knowingly, especially when a simple pointer check can avoid this. I feel it's better for the user to have bad behavior and recover from it than to suddenly be unable to open a file and lose all the data contained within.

I'm happy to add a report print here if you like.

This kind of missing socket stuff occurs either with broken .blend files or, sometimes, when loading stuff between versions and upgrades or downgrades don't work 100%. I don't think normal users who stick to official releases will hit something like this. The version of Blender we publish through our launcher, however, is constantly running on top of main. We at times ingest bugs that are the result of refactors or other upstream changes, but are not always able to ingest the subsequent fixes as quickly. I am 200% against allowing the application to get into a crash state knowingly, especially when a simple pointer check can avoid this. I feel it's better for the user to have bad behavior and recover from it than to suddenly be unable to open a file and lose all the data contained within. I'm happy to add a report print here if you like.
CharlesWardlaw marked this conversation as resolved
@ -382,0 +465,4 @@
usdtokens::scale, pxr::SdfValueTypeNames->Float2))
{
pxr::GfVec2f scale_val(scale[0], scale[1]);
if (!scale_input.Set(scale_val)) {

Is checking for success during .Set really necessary for these and the ones below? Other .Set calls don't seem to check and these aren't completely untrusted inputs that would fail I think.

Is checking for success during `.Set` really necessary for these and the ones below? Other .Set calls don't seem to check and these aren't completely untrusted inputs that would fail I think.
Author
Member

Spoke with Michael and we agree-- we can remove the checks here, as the block is only opened on successful param creation.

Spoke with Michael and we agree-- we can remove the checks here, as the block is only opened on successful param creation.
CharlesWardlaw marked this conversation as resolved
@ -511,0 +642,4 @@
static pxr::TfToken get_node_tex_image_wrap(bNode *node)
{
if (node->type != SH_NODE_TEX_IMAGE) {
std::cout << "get_node_tex_image_wrap() called with unexpected type.\n";

Leftover debug trace?

Leftover debug trace?
CharlesWardlaw marked this conversation as resolved

Let's move the 'report' topic outisde of comments.

WM_report

This API is intrinsically not threadsafe, therefore using it in any code called from wmJob execution callback (or any other thread) is a mistake per se.

It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with BKE_report and passing around reportlists.

And yes, I would love to get this reportlist storage better centralized, but there is no 'universal' USD I/O context available everywhere from USD code currently, at least I could not find anything like that. Could be a good more general refactor to implement at some point though.

CLOG vs. Report

Reports are shown to user, typically as pop-ups + temporary text in the header, in the UI. So there should be a very limited amount of these (ideally only one for a whole USD I/O operation e.g.), synthesizing the issues to report.

Then user can look at the console for the detailed info, generated by CLOG (warnings and errors are always printed out to console, regardless of the --log-level value).

Also not (yet) ideal, I would consider the Blendfile reading code a fairly good example:

  • Using a dedicated struct to gather user-level important reporting info (BlendFileReadReport).
  • Generating a limited amount of reports at the end based on these data (file_read_reports_finalize()) (which also uses a mix of reports and clog btw).
  • Using CLOG in readfile.cc code itself for all kind of detailed immediate info, including reports, warnings and just debugging data.
Let's move the 'report' topic outisde of comments. ## `WM_report` This API is intrinsically not threadsafe, therefore using it in any code called from `wmJob` execution callback (or any other thread) is a mistake _per se_. It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with `BKE_report` and passing around reportlists. And yes, I would love to get this reportlist storage better centralized, but there is no 'universal' USD I/O context available everywhere from USD code currently, at least I could not find anything like that. Could be a good more general refactor to implement at some point though. ## CLOG vs. Report Reports are shown to user, typically as pop-ups + temporary text in the header, in the UI. So there should be a very limited amount of these (ideally only one for a whole USD I/O operation e.g.), synthesizing the issues to report. Then user can look at the console for the detailed info, generated by CLOG (warnings and errors are always printed out to console, regardless of the `--log-level` value). Also not (yet) ideal, I would consider the Blendfile reading code a fairly good example: * Using a dedicated struct to gather user-level important reporting info (`BlendFileReadReport`). * Generating a limited amount of reports at the end based on these data (`file_read_reports_finalize()`) (which also uses a mix of reports and clog btw). * Using `CLOG` in `readfile.cc` code itself for all kind of detailed immediate info, including reports, warnings and just debugging data.
Author
Member

It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with BKE_report and passing around reportlists.

I get the issues you were trying to solve for sure-- it's the intrusive requirement to pass around the ReportList that I'm bumping on here.

Why not "register" handles to specific lists and use that handle instead of forcing the modification of every function signature down the stack?

> It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with `BKE_report` and passing around reportlists. I get the issues you were trying to solve for sure-- it's the intrusive requirement to pass around the ReportList that I'm bumping on here. Why not "register" handles to specific lists and use that handle instead of forcing the modification of every function signature down the stack?

It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with BKE_report and passing around reportlists.

I get the issues you were trying to solve for sure-- it's the intrusive requirement to pass around the ReportList that I'm bumping on here.

Why not "register" handles to specific lists and use that handle instead of forcing the modification of every function signature down the stack?

Not sure what you mean here? The reportlist is stored in USDExportParams/USDImportParams (via the wmJobWorkerStatus *worker_status member), so that it's available as much as possible in USD IO code. Again, afaik there is no universal data shared/accessible to all USD IO code currently.

I Chose the Params structs as they seem to be the most widely shared among the codebase currently, arguably this is not the best place to put this job status data though. I would much rather see something like the USDExporterContext made available to the whole exporter code, and then store the wmJobWorkerStatus data in it (and have something similar for the importer as well).

And I kinda fail to see the point of this discussion in this PR honestly, this is the current system and way to go. You're much welcome to suggest a better system in a dedicated design task obviously.

> > It usually won't give too bad effects, except when code starts spamming tens (if not hundreds) of reports, like USD I/O code can easily do currently with relatively complex files. So use new system, with `BKE_report` and passing around reportlists. > > I get the issues you were trying to solve for sure-- it's the intrusive requirement to pass around the ReportList that I'm bumping on here. > > Why not "register" handles to specific lists and use that handle instead of forcing the modification of every function signature down the stack? Not sure what you mean here? The reportlist is stored in `USDExportParams`/`USDImportParams` (_via_ the `wmJobWorkerStatus *worker_status` member), so that it's available as much as possible in USD IO code. Again, afaik there is no universal data shared/accessible to all USD IO code currently. I Chose the `Params` structs as they seem to be the most widely shared among the codebase currently, arguably this is not the best place to put this job status data though. I would much rather see something like the `USDExporterContext` made available to the whole exporter code, and then store the `wmJobWorkerStatus` data in it (and have something similar for the importer as well). And I kinda fail to see the point of this discussion in this PR honestly, this is the current system and way to go. You're much welcome to suggest a better system in a dedicated design task obviously.
Charles Wardlaw added 3 commits 2023-11-17 22:35:25 +01:00
Jesse Yurkovich approved these changes 2023-11-21 09:19:59 +01:00
Jesse Yurkovich left a comment
Member

This looks good from my side now. Spot checked functionality with the provided repro as well as the Intel Moore's Lane demo asset and confirmed that we're now getting Mapping nodes added as appropriate.

This looks good from my side now. Spot checked functionality with the provided repro as well as the Intel Moore's Lane demo asset and confirmed that we're now getting Mapping nodes added as appropriate.

@CharlesWardlaw If it's not too much trouble, could your run make format on the code, please?

@CharlesWardlaw If it's not too much trouble, could your run `make format` on the code, please?

@blender-bot build

@blender-bot build
Charles Wardlaw added 1 commit 2023-11-21 15:41:35 +01:00
Ran make format
All checks were successful
buildbot/vexp-code-patch-coordinator Build done.
6de1997375

@blender-bot build

@blender-bot build

@blender-bot build

@blender-bot build
Michael Kowalski approved these changes 2023-11-21 23:27:17 +01:00
Michael Kowalski left a comment
Member

This looks good. I've also verified that the mapping node IO works with the attached TextureWrapModes.usdz file. Thank you, Charles.

This looks good. I've also verified that the mapping node IO works with the attached TextureWrapModes.usdz file. Thank you, Charles.

@blender-bot build

@blender-bot build
Michael Kowalski merged commit 925fb66693 into main 2023-11-27 17:18:56 +01:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset System
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Code Documentation
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Viewport & EEVEE
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Asset Browser Project
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Module
Viewport & EEVEE
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Severity
High
Severity
Low
Severity
Normal
Severity
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#114821
No description provided.