RenderDoc for Ruby

Ruby

renderdoc provides Ruby bindings for RenderDoc's in-application API. Use it to capture a specific block of GPU work, schedule frame captures, annotate captures, and open the replay UI from a Ruby application.

The gem binds to RenderDoc but does not bundle, load, or inject RenderDoc itself.

Requirements

  • Ruby 3.2 or newer
  • RenderDoc with in-application API 1.6.0 or newer
  • Linux or Windows

The only runtime gem dependency is ffi, which Bundler installs automatically. RenderDoc does not officially support macOS, so RenderDoc.available? always returns false there. Use Xcode GPU Frame Capture for Metal applications.

Installation

Add the gem to your Gemfile:

gem "renderdoc"

Until the first RubyGems release, install it directly from GitHub:

gem "renderdoc", github: "ydah/renderdoc"

Then run:

bundle install

Loading RenderDoc

ffi lets Ruby call native functions; it does not place librenderdoc.so or renderdoc.dll in the process. The RenderDoc library must already be loaded before this gem can use its API.

Platform How to load RenderDoc
Linux Launch the application from RenderDoc, or use LD_PRELOAD
Windows Launch the application from RenderDoc so renderdoc.dll is injected
macOS Unsupported by RenderDoc

For a direct Linux launch:

LD_PRELOAD=librenderdoc.so bundle exec ruby app.rb

Use an absolute library path if librenderdoc.so is not on the dynamic loader's search path:

LD_PRELOAD=/path/to/librenderdoc.so bundle exec ruby app.rb

When the application is launched from the RenderDoc UI, do not also set LD_PRELOAD. Requiring this gem without RenderDoc attached is safe: RenderDoc.available? returns false.

Quick start

require "renderdoc"

if RenderDoc.available?
  RenderDoc.capture_path_template = "captures/my_app"

  RenderDoc.capture(title: "Bloom pass") do
    renderer.render(scene, camera)
  end
end

RenderDoc.capture returns the block's result. If the block raises an exception, the gem discards the capture and re-raises the original exception. Nested and overlapping captures raise RenderDoc::CaptureError.

Capturing frames

Capture a block

By default, RenderDoc uses wildcard device and window pointers. Supply native pointers when a particular device or window must be selected:

RenderDoc.capture(device: device_pointer, window: window_pointer) do
  renderer.render(scene, camera)
end

For Vulkan, device must be the dispatch-table pointer described by RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE, not the VkInstance handle itself.

Trigger upcoming frames

Schedule one or more captures without wrapping render code:

RenderDoc.trigger_capture
RenderDoc.trigger_capture(frames: 3)

Captures and replay UI

RenderDoc.api_version
# => [1, 6, 0] or a newer compatible version

RenderDoc.captures.each do |capture|
  puts "#{capture.path} at #{capture.timestamp.iso8601}"
end

RenderDoc.comments = "scene=atrium commit=abc123"
RenderDoc.set_capture_comments("regression case", path: "/tmp/frame.rdc")

pid = RenderDoc.launch_replay_ui(connect: true)
RenderDoc.show_replay_ui if RenderDoc.target_control_connected?

Capture#timestamp is a UTC Time. Paths are UTF-8 strings reported by RenderDoc; a listed capture may have been deleted after it was recorded.

Options, overlay, and keys

RenderDoc.overlay = false
RenderDoc.capture_keys = [:f12]
RenderDoc.focus_toggle_keys = [:f11]

RenderDoc.option(:api_validation, true)
RenderDoc.option(:delay_for_debugger, 3)
RenderDoc.option_value(:api_validation)

Option names from the API 1.6.0 header are accepted in snake case. Input buttons include function keys, letters, digits, navigation keys, :print_screen, and :pause. Unknown names and invalid values raise ArgumentError before entering FFI.

Running without RenderDoc

By default, API calls other than available? raise RenderDoc::NotAttachedError when RenderDoc is absent. Applications where capture support is entirely optional can enable no-op behavior:

RenderDoc.configure(unavailable: :noop)

RenderDoc.capture { renderer.render(scene, camera) } # still renders
RenderDoc.trigger_capture                            # => false
RenderDoc.captures                                   # => []
Error Meaning
RenderDoc::NotAttachedError RenderDoc is not loaded in the process
RenderDoc::VersionError The loaded RenderDoc cannot provide API 1.6.0
RenderDoc::CaptureError A capture failed or another capture is in progress

Frame-loop integration

RenderDoc::FrameTrigger parses STAGECRAFT_RENDERDOC=frame:N and triggers exactly once. A frame loop such as stagecraft's can soft-require the gem and continue working when it is not installed:

begin
  require "renderdoc"
  renderdoc_trigger = RenderDoc::FrameTrigger.from_environment
rescue LoadError
  renderdoc_trigger = nil
end

frame_number = 0
loop do
  renderdoc_trigger&.tick(frame_number)
  renderer.render
  frame_number += 1
end

For example, set STAGECRAFT_RENDERDOC=frame:120 before launching the application through RenderDoc to capture frame 120.

RSpec failure captures

Require the optional integration from spec_helper.rb:

require "renderdoc/rspec"

Tag examples whose render work can safely run a second time:

it "renders the bloom pass", :renderdoc_on_failure do
  render_and_compare
end

When a tagged example fails and RenderDoc is attached, the integration runs the example body once more inside RenderDoc.capture, preserves the original failure, and reports the resulting .rdc path. Do not use the tag for examples with non-repeatable external side effects.

Development

Run the unit suite:

bundle exec rake

The Linux integration spec is tagged :renderdoc and excluded from the default suite. CI runs it under renderdoccmd, Xvfb, SDL2, and Mesa software OpenGL, then verifies the generated .rdc file through the in-application API.

The ABI constants in lib/renderdoc/native/generated.rb are generated from RenderDoc v1.25's renderdoc_app.h. After reviewing an upstream header change, regenerate them with:

ruby script/generate_native_api.rb path/to/renderdoc_app.h lib/renderdoc/native/generated.rb

License

The gem is available under the MIT License. RenderDoc is a separate project and is not distributed with this gem.