Skip to content

Render OSL in pytest with testrender - #85

Merged
ppenenko merged 7 commits into
metashade/devfrom
metashade/pytest-osl
Oct 26, 2026
Merged

ppenenko merged 7 commits into
metashade/devfrom
metashade/pytest-osl

Conversation

@ppenenko

Copy link
Copy Markdown
Member

Follow-up to #82 and #84: the OSL backend now renders, not just generates.

Summary

  • OSL rendering in pytest. OslBackend renders through MaterialX's OslRenderer and OSL's testrender when the OSL_ROOT environment variable points at an OSL install (bin/oslc, bin/testrender). Without OSL_ROOT it stays generation-only, so CI behaves as before.
  • Scratch directory. The compiled utility shaders and testrender's intermediate files (.oso, scene XML, error logs) go to a per-session temporary directory. Only the *_osl.png images land next to the baselines, and those are gitignored.
  • Backend._prepare_program hook. Called before createProgram for per-shader renderer setup. OslRenderer keeps the shader name and output name/type as renderer state, so OSL sets them here; the base implementation is a no-op, and GLSL is unchanged.
  • VS Code task "Run MaterialXTest [renderosl]" for the C++ OSL render tests. MaterialX's existing find_package(OSL) picks up OSL_ROOT at configure time, so no CMake changes are needed.
  • README: documents OSL_ROOT and the current limitations.

Known limitations

OSL renders don't match MaterialXTest's yet, because some OslRenderer settings lack Python bindings:

  • setAaLit / setAaUnlit: renders use testrender's default of one sample per pixel, so glossy materials are much noisier.
  • setEnvShaderParameterOverrides: the radiance map is baked into a scratch copy of envmap.osl instead; testrender crashes on an empty environment map filename.
  • GenOptions.oslConnectCiWrapper isn't set, and MaterialXTest's tangent/bitangent implementation overrides aren't registered.

Adding these bindings is a separate C++ change, and a candidate for upstream.

Testing

Windows, OSL 1.14.10 built with OSL_build:

  • pytest contrib/tests/test_render.py -m osl -n 8 with OSL_ROOT set: all 100 test cases pass and render 186 images, in both developer mode and --output-dir (CI) mode, with the .osl baselines unchanged.
  • Without OSL_ROOT: the OSL tests pass, generation only. The GLSL tests are unaffected.
  • MaterialXTest "[renderosl]": all 497 assertions pass.

Adds "Run MaterialXTest [renderosl]", mirroring the [renderglsl] task.

The OSL render tests need oslc and testrender. MaterialX's existing
find_package(OSL) fills in their paths when OSL_ROOT points at an OSL
install at configure time; no PATH changes are needed at runtime.
Without OSL_ROOT, the OSL tests skip themselves as before.
OslBackend now renders through OslRenderer when OSL_ROOT points at an
OSL install, and stays generation-only otherwise. testrender's
intermediate files and the compiled utility shaders go to a per-session
scratch directory rather than next to the committed baselines.

Backend.process gains a _prepare_program hook for per-shader renderer
setup; OSL uses it to set the shader name and output.

Known gaps, pending Python bindings:
- setEnvShaderParameterOverrides: the radiance map is baked into a
  scratch copy of envmap.osl instead (testrender crashes on an empty
  environment map filename).
- setAaLit/setAaUnlit: renders use testrender's default of 1 sample per
  pixel, so glossy materials are much noisier than in MaterialXTest.
- GenOptions.oslConnectCiWrapper, and the tangent/bitangent
  implementation overrides MaterialXTest registers.
Explain OSL_ROOT, where testrender's intermediate files go, and the
limitations pending OslRenderer Python bindings.
Index the output block directly and lay out the type table one entry per line.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved rendering correctness issues can produce incorrect OSL images despite successful test execution.

4 open issues
What changed in this PR

Extends the pytest OSL backend introduced in #82 and #84 from shader generation to optional rendering through MaterialX’s OslRenderer and OSL’s testrender.

Changes:

  • Enables OSL rendering when OSL_ROOT is set, with intermediate files stored in temporary directories.
  • Adds a per-shader renderer preparation hook.
  • Documents setup and limitations, and adds a VS Code task for C++ OSL render tests.
File Description
contrib/​tests/​rendertest/​backends.py Adds OSL renderer setup, scratch storage, and per-shader configuration.
contrib/​tests/​README.md Documents optional OSL rendering and known limitations.
.vscode/​tasks.json Adds a task for running MaterialXTest’s OSL render tests.

Review effort: Balanced

Comment thread contrib/tests/rendertest/backends.py Resolved
Comment thread contrib/tests/rendertest/backends.py Resolved
Comment thread contrib/tests/rendertest/backends.py Resolved
Comment thread contrib/tests/rendertest/backends.py Resolved
Addresses review feedback on the OSL backend:

- Don't vertically flip saved OSL images. Only OpenGL readbacks are
  bottom-up; OSL captures are read from testrender's PNG, as in
  MaterialXTest's OSL tester.
- Resolve texture filenames against the document's search path and pass
  them to testrender as shader parameter overrides. testrender resolves
  them from its working directory, so document-relative paths didn't
  load. The dumped baselines keep the authored paths.
- Clear the image cache before each render. Materials that share a
  shader name (e.g. the StandardSurface and OpenPbr "Default") reuse the
  same scratch image path, so a worker could save a stale image.
- Bake the radiance map into raytype_background.osl too, so secondary
  rays hitting the background quad sample the environment map.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unquoted renderer commands break OSL rendering when temporary or installation paths contain spaces.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Quote OSL renderer command paths containing spaces

contrib/​tests/​rendertest/​backends.py:297

OSL rendering fails when the system temporary directory or OSL_ROOT contains spaces, such as a Windows user profile or C:/Program Files/OSL. OslRenderer builds unquoted shell commands for the executable, source, output, log, and shader-search paths (source/MaterialXRenderOsl/OslRenderer.cpp:167–173,437–442); this also affects the executable paths set at lines 279–282. Add platform-appropriate quoting at the renderer's command-invocation boundary so both scratch and installation paths remain intact.

4 resolved since last review

Review effort: Balanced

OslRenderer builds unquoted oslc and testrender command lines, so an
OSL_ROOT or temporary directory with spaces breaks rendering with
obscure errors. Fail early with a clear message instead; quoting
belongs in OslRenderer itself.
@ppenenko

Copy link
Copy Markdown
Member Author

Re the previously missed issue (unquoted OslRenderer command paths): agreed. The root cause is in C++. OslRenderer::renderOSL and compileOSL build std::system command lines with only the -I paths quoted. Pre-quoting from Python would be fragile under cmd /c quote stripping, so cf640d6 fails fast with a clear error when OSL_ROOT or the scratch directory contains spaces, and documents the limitation in the README. Proper quoting will go into the planned C++ follow-up that adds the missing OslRenderer Python bindings.

@ppenenko

Copy link
Copy Markdown
Member Author

Filed upstream: AcademySoftwareFoundation#3120

@ppenenko
ppenenko merged commit c5c5bbd into metashade/dev Oct 26, 2026
29 of 36 checks passed
@ppenenko
ppenenko deleted the metashade/pytest-osl branch October 26, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants