Skip to content

Ogre 1.12 support to replace ogre 1.9#1291

Open
j-rivero wants to merge 24 commits intomainfrom
jrivero/ogre112-pr2-combined
Open

Ogre 1.12 support to replace ogre 1.9#1291
j-rivero wants to merge 24 commits intomainfrom
jrivero/ogre112-pr2-combined

Conversation

@j-rivero
Copy link
Copy Markdown
Contributor

@j-rivero j-rivero commented May 5, 2026

🎉 New feature

This is a large PR coming from #1281.

Summary

The ogre 1.9 support is dropped in favor of the ogre 1.12. This requires changes in release-tools for Windows and homebrew-simulation to get the testing done correctly on Mac/Windows, Linux and GitHub actions can work from these changes AFAIK. No Mac ogre formula by now.

Reviewing process

Easiest way to review could be done per-commit. I can technically review some of them that are not related to changes in rendering and affects more to deprecation and other C++ stuff.

Non deep rendering changes in commits that I reviewed (in order)

OGRE_MEDIA_PATH

On OGRE 1.9, OgreShadowVolumeExtrudeProgram::initialise() wasn't a hard requirement at engine init — stencil-shadow programs were either compiled-in or lazily loaded only if used. Stock TerrainMaterialGeneratorA likewise didn't require an external OgreUnifiedShader.h include path. So gz-rendering could afford to ignore the OGRE-package Media tree entirely.

OGRE 1.12 made Media/ShadowVolume/*.program and Media/Terrain/OgreUnifiedShader.h mandatory at engine init.

Worth looking by a rendering expert (8 commits)

A batch of important changes collected by the IA agent to be reviewed in this category:

ee46cd46 — PSSM shadow caster window-space depth (correctness of all shadowed scenes).
076fd2d4 — depth/GPU-lidar per-object matrix push (correctness of all depth sensors); also skips GpuRaysTest.Heightmap on ogre per #35 (laser-pass + stock TerrainMaterialGeneratorA on OGRE 1.12 returns 0 against the bowl heightmap; ogre2 unaffected).
2ab4a527 — projector decal rewrite (all decal/projector scenes).
4eb9efce — heightmap stock TerrainMaterialGeneratorA swap, including pushing layer texture basenames so the SM2Profile lookup hits the registered archive (all terrain scenes).
d9eb98f3 + 897c8cd9 — texture upload byte order + 16-bit clamp (all textured visuals; coupled).

Checklist

  • Signed all commits for DCO
  • Added a screen capture or video to the PR description that demonstrates the feature
  • Added tests
  • Added example and/or tutorial
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • Consider updating Python bindings (if the library has them)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • Updated Bazel files (if adding new files). Created an issue otherwise.
  • While waiting for a review on your PR, please help review another open pull request to support the maintainers
  • Was GenAI used to generate this PR? If so, make sure to add "Generated-by" to your commits. (See this policy for more info.)

Generated-by: Claude Sonnet 4.6, GPT 5.4, and Claude Opus 4.7.

Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining Signed-off-by and Generated-by messages.

Backports: If this is a backport, please use Rebase and Merge instead.

j-rivero and others added 12 commits May 5, 2026 12:39
Replaces the previous 1.9.0 floor with 1.12.10 to match the OGRE
package shipped on Ubuntu Noble. The OGRE2 backend remains discovered
via gz_find_package(GzOGRE2) and is unchanged.

Originates from the temporary local-build commit 70c0df8 ("Forcing
CMAKE, undo") on jrivero/ogre112; the debugging hack that forced
OGRE2_FOUND=FALSE has been stripped.

Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
OGRE 1.12's ResourceManager::create() now throws if the material already
exists (changed from silently returning it). The fix is to use
createOrRetrieve().

Need to track ownership to avoid duplicate removals.

Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
…it shouldn't be removed

Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
setSourceFile() is called with the full absolute path, but OGRE 1.12
resolves it through the resource group (where it's registered just as
depth_points_vs.glsl). Also, addResourceLocation is being called on the
file path instead of its parent directory.

The fix: add the parent directory (not the file) to resource locations,
and pass just the basename to setSourceFile()

Generated-by: Claude Sonnet 4.6
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
The Fonts resource group is registered without recursive search, so font
subdirectories (liberation-sans/, roboto/) aren't scanned. Fix: enable
recursive in addResourceLocation for Fonts.

Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
ShadowVolume program files exist at /usr/share/OGRE/Media/ShadowVolume/
but aren't added as a resource location

Generated-by: Claude Sonnet 4.6
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
…lper

SetNormalMap was a TODO; it stored the name/data but never created the
OGRE texture, so RTSS NormalMapLighting::setNormalMapTextureName(_name)
would bind to a non-existent texture.

Extract the createManual + pixel-upload block from SetTextureDataImpl
into CreateOgreTextureFromImage and reuse it from SetNormalMap. The
helper does not touch ogreTexState, so the diffuse slot is left alone.

This alone is not enough to flip MeshTest.NormalMapWithoutTexCoord from
partial-fail to pass — submesh 1 (no texcoords) still bleeds blue — but
it removes a real load-bearing TODO that will be needed by any final
fix. See remaining-failures.md Part B for the follow-up investigation
notes.

Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
ProjectorTest.Visibility was producing all-black pixels from both cameras
because OGRE 1.12 on Ubuntu Noble ships without a PNG codec
(Ogre.log: "Can not find codec for 'png' format. Supported formats are:
astc bsp dds ktx mesh pkm"). Every texture loaded by file name -- the
projector decal textures and projection_filter.png -- silently returned a
blank sampler, so the decal fragment shader sampled zero and collapsed
output to (0,0,0). Earlier investigations chased RTSS/scheme-switch
theories; the OGRE log made the real cause obvious.

Fix:
* Add file-scope LoadImageAsOgreTexture() helper that decodes PNGs via
  gz-common::Image and uploads raw RGBA to a manual OGRE texture under an
  alias, mirroring OgreMaterial::CreateOgreTextureFromImage.
* AddDecalToMaterial() now preloads both the projector decal and
  projection_filter.png under per-projector aliases, resolving
  projection_filter.png to an absolute path via the resource group
  manager, then attaches the pass's texture units by alias.
* The decal pass now uses a pair of hand-written GLSL programs
  (__gz_ProjectorDecalVS / __gz_ProjectorDecalFS) that do projective
  texturing explicitly via texture_viewproj_matrix{0,1} auto-constants,
  bypassing RTSS's FFP_GenerateTexCoord_Projection path. The programs are
  created once per process and reused across projector instances.
* The clone's wall pass now goes through
  ShaderGenerator::createShaderBasedTechnique(*clone, Default, "projector")
  + validateMaterial + clone->load() so it has GPU programs under GL3+
  core. handleSchemeNotFound() returns that "projector"-scheme technique
  via a new ProjectorSchemeTechnique() helper instead of the unloaded
  fixed-function default that GL3+ cannot execute.

Verified: INTEGRATION_projector (Visibility + Heightmap) passes under
GZ_ENGINE_TO_TEST=ogre on the gl3plus render system.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
MeshTest.NormalMapWithoutTexCoord was rendering the bit-complement of the
expected colours (red source -> yellow, green source -> magenta). Root
cause: the manual texture was created with PF_R8G8B8A8 (OGRE packed word
0xRRGGBBAA, little-endian memory order {A,B,G,R}) but uploaded from
gz-common::Image::RGBAData() (memory order {R,G,B,A}) after an extra R<->B
swap. Sampling the memory as PF_R8G8B8A8 yielded the pixel
(R=A_src, G=B_src, B=G_src, A=R_src) -- the exact inversion the test
dumps showed.

Fix: upload via PF_BYTE_RGBA (aliases to PF_A8B8G8R8 on LE, memory
{R,G,B,A}) and drop the now-redundant swap, matching RGBAData() directly.

Also remove the GITHUB_ACTIONS-gated CHECK_UNSUPPORTED_ENGINE skip added
upstream for this test on Noble; with the texture fix the test passes on
ogre_gl3plus both locally and on CI-equivalent Noble.

Verified: MeshTest.NormalMapWithoutTexCoord passes under
GZ_ENGINE_TO_TEST=ogre.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
j-rivero and others added 11 commits May 7, 2026 19:51
For the no-custom-material path, CreateMaterial was installing our
TerrainMaterial("Default/White") generator, whose Profile is not an
SM2Profile. SetupShadows then could not apply the SM2Profile setters
without writing past the real Profile (the heap corruption that
commit ed774fd masked with a dynamic_cast that silently skipped
the setters entirely).

Stop installing TerrainMaterial for that path so TerrainGlobalOptions
falls back to the stock TerrainMaterialGeneratorA, whose active
Profile is an SM2Profile. The dynamic_cast now resolves to a real
SM2Profile and the shadow setters actually run, while remaining
defensive for the custom-material-name branch (where TerrainMaterial
is still installed and the cast correctly returns nullptr).

The stock TerrainMaterialGeneratorA shaders #include TerrainTransforms
and TerrainHelpers, so the Terrain/ subdirectory of OGRE/Media is now
added as a resource location alongside ShadowVolume.

Fixes UNIT_Heightmap_TEST and ProjectorTest.Heightmap against OGRE
1.12.10. Supersedes ed774fd's workaround.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
OGRE 1.12's SceneManager no longer calls RenderSystem::_setWorldMatrix
per renderable, so the legacy GLSL shaders used by the depth camera
(depth_points_vs.glsl: point = gl_ModelViewMatrix * gl_Vertex) and the
gpu rays 1st pass (gpu_rays_1st_pass_vs.glsl: same pattern) saw a
stale modelview matrix from a previous frame or the view matrix only,
producing all-inf depth and zero ranges.

Re-push the renderable's world transform through the GL fixed-function
state from a RenderObjectListener so gl_ModelViewMatrix resolves to
view*world each object:

  - OgreDepthCamera now inherits Ogre::RenderObjectListener; the
    listener calls renderSys->_setWorldMatrix(rend's world transform).
    Listener is added around the pcdTexture update in Render().

  - OgreGpuRays's existing listener gains the same _setWorldMatrix
    call, placed last so nothing else perturbs the GL MODELVIEW stack.
    UpdateRenderTarget seeds the view/projection state first.

  - The 2nd pass canvas needs its own world matrix too, so the
    listener stays active; currentMat/currentTexture are switched to
    the 2nd-pass material before the final UpdateRenderTarget, and the
    retro/max/min named-constant setters are gated to the 1st-pass
    material which is the only one that declares them.

Fixes INTEGRATION_depth_camera (DepthCameraBoxes), INTEGRATION_gpu_rays
and INTEGRATION_lidar_visual against OGRE 1.12.10.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
OGRE 1.12.10's GLSL frontend runs the fragment shader at #version 150
against a core GL context, where textureCube was removed after 140:

  'GerstnerWaves_fs.glsl' 0(52) : error C7616: global function
  textureCube is removed after version 140

The shader compile error left the wave material with no supportable
technique, so the test saw the blue sky background instead of the
animated water. Switch the cubemap lookup to the version-agnostic
texture(samplerCube, vec3, lod) overload.

Fixes INTEGRATION_waves (WavesTest.Waves) against OGRE 1.12.10.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
The shadow_caster_fp shader was writing depth as vertex_depth.z/vertex_depth.w,
which is the NDC z in [-1, 1]. The OGRE 1.12 RTSS IntegratedPSSM receiver
shader (SGXLib_IntegratedPSSM.glsl) applies shadowMapPos.z = z*0.5 + 0.5 on
the receiver side and then compares against the sampled shadow map value,
so it expects the shadow map to store depth in window-space [0, 1].

Because of the mismatch, the depth test always resolved to "in shadow" for
every receiver fragment, which dropped the directional-light diffuse term
to zero over the whole image. INTEGRATION_shadows observed identical sums
for the shaded and unshaded halves because neither half ever received
direct light.

Switch the caster to gl_FragCoord.z, which the rasterizer has already
mapped to [0, 1] for us and which matches the receiver's expected range.

Fixes INTEGRATION_shadows (ShadowsTest.Shadows) against OGRE 1.12.10.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Two coupled bugs prevented mesh textures from binding under OGRE
1.12 on Ubuntu Noble:

1. Path-vs-basename. SetTextureImpl was calling
   addResourceLocation(_texture, ...) with the full file path and
   then setTextureName(_texture). OGRE indexes resource locations
   as directories, keyed by basename, so the absolute-path lookup
   never resolved and every texture became "Cannot locate resource
   /abs/path/foo.png in resource group General". Same shape as the
   shader fix from commit 526304e; this path was missed at the
   time.

2. Missing PNG/JPG codecs. Even after path resolution is correct
   (e.g. via GZ_SIM_RESOURCE_PATH adding the Fuel-cache mesh dir
   recursively), OGRE 1.12 on Noble has no PNG or JPG codec
   ("Can not find codec for 'png' format" in Ogre.log). Resolving
   the basename to the on-disk file then errored at prepare time
   and the texture stayed blank. Commit 1206549 worked around
   this for projector decals by pre-decoding through
   gz-common::Image and uploading raw RGBA via createManual; the
   mesh path needed the same workaround.

Fix: SetTextureImpl now tries gz-common::Image::Load on the path
first. If it succeeds, the texture is uploaded as a manual OGRE
texture under its basename in the default resource group, and
setTextureName picks it up directly from TextureManager — no
codec or resource-group prepare step involved. If the image
cannot be decoded by gz-common (e.g. native OGRE-only formats
such as DDS or KTX), the parent directory is registered with the
material's resource group and the basename is set, matching the
526304e shader pattern. The createManual call deliberately
bypasses CreateOgreTextureFromImage so its
ResourceGroupManager::resourceExists short-circuit (which fires
once GZ_SIM_RESOURCE_PATH has indexed the dir) does not block
the upload.

Verified end-to-end with two complex Fuel models (Warehouse,
deliverybot) loaded into a gz-sim camera-sensor world:

  GZ_SIM_RESOURCE_PATH=$HOME/.gz/fuel/.../warehouse/5:\
                      $HOME/.gz/fuel/.../deliverybot/2 \
    gz sim -s -r --iterations 300 \
      --render-engine-server ogre /tmp/fuel_warehouse_world.sdf

Before: every Fuel-model texture failed with either "Cannot
locate resource ..." or "Can not find codec for ... format" in
Ogre.log; mesh visuals rendered untextured.
After: zero texture errors, all five warehouse textures plus
DeliveryBot_Diffuse upload as manual textures, the simulation
runs 300 iterations and shuts down cleanly.

Regression: full gz-rendering ctest suite still passes (134/134),
including the existing INTEGRATION_mesh and
INTEGRATION_projector tests that exercise SetTexture-related
paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Real-world Fuel scenes (jetty_demo) crashed the server with
"Fatal glibc error: malloc.c:4179 (_int_malloc): assertion
failed: chunk_main_arena (bck->bk)" when the Sensors render
thread loaded models with embedded 16-bit-per-channel PNG
textures (e.g. open-rmf/conveyor_block normal map: 512x512 PNG
with bit_depth=16, color_type=2).

Root cause: gz::common::Image::RGBAData() honours the source
image's bits_per_channel — for an 8-bit source it returns
w*h*4 bytes, for a 16-bit source it returns w*h*8 bytes. Both
SetTextureImpl (commit b9e0db7) and CreateOgreTextureFromImage
(commit 8b23d54 + 9e4d20f) created the OGRE texture with
PF_BYTE_RGBA (which allocates exactly w*h*4 bytes) and then
memcpy(pixelBox.data, data.data(), data.size()), which
overflowed the OGRE pixel buffer by a factor of 2 for any
16-bit input. The overflow corrupted heap free-list metadata of
adjacent chunks; the corruption surfaced later as an abort
inside libnvidia-glcore's calloc when the next big GL upload
walked the corrupted free chunk.

Fix: cap the memcpy at the PF_BYTE_RGBA buffer size (w*h*4) and
handle the 16-bit case by extracting the most-significant byte
of each native-endian uint16_t channel (stbi stores 16-bit PNGs
as native uint16_t, so on little-endian hosts the MSB sits at
offset 1). Anything else logs a gzwarn and skips the upload —
strictly better than silently corrupting the heap.

The Heightmap (commit ed774fd) and Projector (commit 1206549)
manual-upload paths use the same lock+memcpy pattern but are
not changed here: their inputs are sourced from controlled
8-bit channels and have not been observed to hit this bug.

Backtrace at the crash site (server alone, gdb --batch):
 #21  Ogre::GLTextureBuffer::upload(...)
 #24  OgreMaterial::CreateOgreTextureFromImage at OgreMaterial.cc:820
 #25  BaseMaterial<>::CopyFrom at BaseMaterial.hh:1105
 #26  OgreMeshFactory::LoadImpl at OgreMeshFactory.cc:442
 #29  OgreScene::CreateMeshImpl  ("conveyor_block_visual.glb")
 #33  RenderUtil::Update
 #35  SensorsPrivate::RenderThread

Verified with:
  gz sim --verbose --render-engine-server ogre \
         --render-engine-gui ogre \
         /home/jrivero/code/gz/jetty_demo/.../jetty.sdf
runs cleanly to the 60 s timeout — no SIGSEGV, no SIGABRT, no
glibc fatal, no backtrace dump in stderr; both -s server-only
and full server+GUI on display :1 confirmed.

Regression sweep clean: 134/134 gz-rendering ctest pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
The ShadowVolume / Terrain media lookup hard-coded /usr/share/OGRE/Media
and /usr/local/share/OGRE/Media, which fails on Windows, macOS, Fedora,
Conda, NixOS — every platform that doesn't follow Debian-style FHS.
OGREConfig.cmake exports OGRE_MEDIA_DIR and is loaded transitively by
gz_find_package(GzOGRE), so plumb that value through as a compile
definition (OGRE_MEDIA_PATH) and use it directly. When the value is
unset (older OGRE installs that did not export it), we simply skip the
optional registration; nothing else degrades.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
…push

Three places carried the same bug class as the heap-overflow fix in
OgreMaterial::CreateOgreTextureFromImage (a48b445) but were left
unguarded:

* OgreProjector::LoadImageAsOgreTexture — naive memcpy(data.size())
  against a PF_BYTE_RGBA buffer overflows the heap when fed a
  16-bit-per-channel projector texture (e.g. an embedded 16-bit PNG).
  Mirror OgreMaterial's bytes8 / bytes8*2 / else-warn pattern.

* OgreProjector::AddDecalToMaterial — getByName(_matName) returns null
  on miss in OGRE 1.12 instead of throwing, and this code path is
  reached from the scheme listener with a name that has been trimmed
  ("_MissingTech_" prefix stripped) so the lookup *can* legitimately
  fail. Add a null check before mat->getTechnique(0)->createPass().

* OgreDepthCamera/OgreGpuRays::notifyRenderSingleObject —
  Renderable::getWorldTransforms writes getNumWorldTransforms()
  matrices into the buffer without a size argument. The pre-fix code
  filled the [OGRE_MAX_NUM_BONES] stack array *before* checking the
  count, so a software-skinned renderable whose count exceeds
  OGRE_MAX_NUM_BONES (skinned animated meshes) silently overflowed the
  stack. Check count first, clamp, and skip with a warning when the
  bound is exceeded. Also document the pre-existing limitation that
  only xform[0] is pushed (skinned visuals render at root bone) — a
  full fix requires plumbing the bone-matrix array into the depth/laser
  GLSL programs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
@j-rivero j-rivero force-pushed the jrivero/ogre112-pr2-combined branch from 287935f to 7c58452 Compare May 7, 2026 17:56
Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com>
@j-rivero
Copy link
Copy Markdown
Contributor Author

j-rivero commented May 8, 2026

I think that now the tests passes, there is a good bunch of compiler warnings mostly related to deprecated stuff. I would prefer not to put more changes here and do it in a next PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

2 participants