Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Python wrapper for NVIDIA OptiX Ray Tracing Engine

License

NotificationsYou must be signed in to change notification settings

ozen/PyOptiX

Repository files navigation

PyOptiX lets you access Nvidia's OptiX Ray Tracing Engine from Python.

PyOptiX wraps OptiX C++ API using an extension that uses Boost.Python library. Python API is similar to C++ API.PyOptiX documentation does not include anything about OptiX, so you should already know OptiX and its C++ API.

Supported Platforms

Only Linux is supported. PyOptiX can work on other platforms but you may need to modify setup.py and setCompiler.nvcc_path andCompiler.extra_compile_args parameters manually during run time.

Supported OptiX Versions

PyOptiX was tested with: OptiX3.9.x,4.x.x,5.0.x, and5.1.0.

Features

Since PyOptiX wraps OptiX C++ API, the API is almost the same. PyOptiX adds couple of new features.Let's talk about them.

Context Stack

PyOptiX implements a Context Stack.Acceleration,Buffer,Geometry,GeometryGroup,GeometryInstance,Group,Material,Program,Selector,TextureSampler,Transform objects are alwayscreated in the activeContext during their instantiation.

AContext is created during initialization automatically.Whenever a new Context object is instantiated, it is pushed to the stack automatically.pyoptix.current_context() method returns the currently active context (which is on top of the stack).Context.pop() instance method pops the context from the stack, so the next context in the stack becomes active.You can keep the popped context in a variable, then push it to the stack again usingContext.push() instance method,making it active. The same Context may occur multiple times in the stack.

Garbage Collection

Lifetime of OptiX objects are tied to lifetime of PyOptiX objects.When Python objects get garbage-collected, OptiX objects are destroyed automatically.

In OptiX, a context is destroyed along with all other types of objects in it, such as buffers and programs.So it's possible in PyOptiX for a wrapper object such aspyoptix.Buffer to become invalid because its underlyingobject was destroyed, but remain accessible.This situation is tracked by PyOptiX and operations on invalid wrapper objects result in RuntimeError. It is theuser's responsibility to use only valid wrapper objects.

Remember that Python implements reference counting and objects are garbage-collected only when reference counts is zero.The Context Stack holds strong references to Contexts.So, a Context would never get garbage-collected when it is in the stack.

All graph nodes hold strong references to their parents and children. Therefore, graph nodes connected to other nodesare not garbage-collected even if no references remain in user's code.

Programs, Buffers and Texture Samplers can be bindless, i.e. there can be accessed using their IDs in device code.Program,Buffer andTextureSampler classes have a property namedbindless, which when set to True preventsdestruction of the underlying OptiX object even if PyOptiX object gets garbage-collected.

PTX Generation

Programs supplied to the OptiX API must be written in PTX. PyOptiXProgram objects are instantiated witha file path and a function name. If the file is a PTX file, PyOptiX does nothing more than calling OptiX functions.If the file is a source file,pyoptix.Compiler class is used to compile the source to PTX,then the Program object is created.

pyoptix.Compiler needs to know some attributes of the system to work correctly. These attributes are collectedduring PyOptiX installation and saved to (1) etc/pyoptix.conf/pyoptix.conf file and (2) pyoptix.conf file in thesame directory with Python executable that was used to execute the setup script. If this process somehow fails, youneed to set Compiler flags manually.

Compiler.nvcc_path must be a valid path to nvcc binary.

Compiler.output_path is where PTX files will be saved. Default is/tmp/pyoptix/ptx/. Default path is createdautomatically if it doesn't exist when module is imported.Custom paths are NOT created automatically, so the user must create it before using.

Compiler.extra_compile_args is a list of arguments passed to nvcc during PTX compilation.

Compiler.use_fast_math is a boolean that adds--use_fast_math flag to compile command if is set to True.

Compiler.add_program_directory(directory) method adds the directory to the list of directories in which the file pathsgiven to Program objects will be searched.

Compiler.remove_program_directory(directory) removes the directory from the list it previously added to.

If the source file given topyoptix.Compiler was compiled to PTX before, Compiler checks if the source file orthe files included in#include "<file>" format changed; recompiles if it detects a change, uses the old PTX otherwise.

Program Cache

Program(file_path, function_name) always creates a new Program object in OptiX.PyOptiX also implements a cache for programs.Program.get_or_create(file_path, function_name) static method returns the cached program if the active Context,file path and function name all match, otherwise creates and returns it.

IfProgram.dynamic_programs class variable is set to True, the source file is recompiled if it was changed, evenif its program was cached, and the program will be recreated using the new PTX.IfProgram.dynamic_programs is set to False, the cached program is returned without change check.

Variable Assignments

OptiX program objects communicate with the host program through variables. API objects to whichprogram variables can be attached are called Scoped objects in PyOptiX. Scoped objects define a dictionary interfacefor variable assignment, actual variable declaration and value assignments are handled automatically.

If the value that is being assigned is an API object, the operation is straightforward. For other types of values,PyOptiX transfers the value to the C++ backend using NumPy arrays. Since NumPy is ubiquitous in Python circles,PyOptiX doesn't abstract away the usage of NumPy arrays.

If the variable is being attached to the program object whosedevice code has the variable's declaration, PyOptiX deduces the type of the variable and casts the valueto NumPy array with proper dtype. If it isn't, PyOptiX cannot deduce the type, therefore the user must cast the value to NumPy array with proper dtype.The conversion between NumPy arrays and OptiX vector types are as follows:

Array dtypeArray ShapeOptiX C++ Type
float32(1, )float
float32(2, )float2
float32(3, )float3
float32(4, )float4
int32(1, )int
int32(2, )int2
int32(3, )int3
int32(4, )int4
uint32(1, )unsigned_int
uint32(2, )unsigned_int2
uint32(3, )unsigned_int3
uint32(4, )unsigned_int4
float32(2, 2)matrix2x2
float32(2, 3)matrix2x3
float32(2, 4)matrix2x4
float32(3, 2)matrix3x2
float32(3, 3)matrix3x3
float32(3, 4)matrix3x4
float32(4, 2)matrix4x2
float32(4, 3)matrix4x3
float32(4, 4)matrix4x4

Buffers using NumPy Arrays

Data is transferred to Buffers using NumPy arrays. Since NumPy is ubiquitous in Python circles,PyOptiX doesn't abstract away the usage of NumPy arrays.

Buffer objects can be created usingBuffer.from_array(numpy_array, buffer_type, drop_last_dim) static method.A buffer object without copying data can be created usingBuffer.empty(shape, dtype, buffer_type, drop_last_dim)static method.

dtype must be a NumPy dtype.

buffer_type is either one of 'i', 'o', or 'io', corresponding toINPUT, OUTPUT, and INPUT_OUTPUT formats.

drop_last_dim is a boolean that indicates that the array holds or will hold a vector type whose length is thesize of the last dimension of the array. For example for a 2D float4 buffer, the NumPy array's shape will be(height, width, 4) and its dtype will be float32. All possible conversions between NumPy arrays and buffers can befound in the following table.

Array dtypeArray Shapedrop_last_dimBuffer FormatBuffer Shape
float32(d0, d1, ..., dn)Falsefloat(d0, d1, ..., dn)
float32(d0, d1, ..., dn-1, 1)Truefloat(d0, d1, ..., dn-1)
float32(d0, d1, ..., dn-1, 2)Truefloat2(d0, d1, ..., dn-1)
float32(d0, d1, ..., dn-1, 3)Truefloat3(d0, d1, ..., dn-1)
float32(d0, d1, ..., dn-1, 4)Truefloat4(d0, d1, ..., dn-1)
int32(d0, d1, ..., dn)Falseint(d0, d1, ..., dn)
int32(d0, d1, ..., dn-1, 1)Trueint(d0, d1, ..., dn-1)
int32(d0, d1, ..., dn-1, 2)Trueint2(d0, d1, ..., dn-1)
int32(d0, d1, ..., dn-1, 3)Trueint3(d0, d1, ..., dn-1)
int32(d0, d1, ..., dn-1, 4)Trueint4(d0, d1, ..., dn-1)
uint32(d0, d1, ..., dn)Falseunsigned_int(d0, d1, ..., dn)
uint32(d0, d1, ..., dn-1, 1)Trueunsigned_int(d0, d1, ..., dn-1)
uint32(d0, d1, ..., dn-1, 2)Trueunsigned_int2(d0, d1, ..., dn-1)
uint32(d0, d1, ..., dn-1, 3)Trueunsigned_int3(d0, d1, ..., dn-1)
uint32(d0, d1, ..., dn-1, 4)Trueunsigned_int4(d0, d1, ..., dn-1)
int16(d0, d1, ..., dn)Falseshort(d0, d1, ..., dn)
int16(d0, d1, ..., dn-1, 1)Trueshort(d0, d1, ..., dn-1)
int16(d0, d1, ..., dn-1, 2)Trueshort2(d0, d1, ..., dn-1)
int16(d0, d1, ..., dn-1, 3)Trueshort3(d0, d1, ..., dn-1)
int16(d0, d1, ..., dn-1, 4)Trueshort4(d0, d1, ..., dn-1)
uint16(d0, d1, ..., dn)Falseunsigned_short(d0, d1, ..., dn)
uint16(d0, d1, ..., dn-1, 1)Trueunsigned_short(d0, d1, ..., dn-1)
uint16(d0, d1, ..., dn-1, 2)Trueunsigned_short2(d0, d1, ..., dn-1)
uint16(d0, d1, ..., dn-1, 3)Trueunsigned_short3(d0, d1, ..., dn-1)
uint16(d0, d1, ..., dn-1, 4)Trueunsigned_short4(d0, d1, ..., dn-1)
int8(d0, d1, ..., dn)Falsebyte(d0, d1, ..., dn)
int8(d0, d1, ..., dn-1, 1)Truebyte(d0, d1, ..., dn-1)
int8(d0, d1, ..., dn-1, 2)Truebyte2(d0, d1, ..., dn-1)
int8(d0, d1, ..., dn-1, 3)Truebyte3(d0, d1, ..., dn-1)
int8(d0, d1, ..., dn-1, 4)Truebyte4(d0, d1, ..., dn-1)
uint8(d0, d1, ..., dn)Falseunsigned_byte(d0, d1, ..., dn)
uint8(d0, d1, ..., dn-1, 1)Trueunsigned_byte(d0, d1, ..., dn-1)
uint8(d0, d1, ..., dn-1, 2)Trueunsigned_byte2(d0, d1, ..., dn-1)
uint8(d0, d1, ..., dn-1, 3)Trueunsigned_byte3(d0, d1, ..., dn-1)
uint8(d0, d1, ..., dn-1, 4)Trueunsigned_byte4(d0, d1, ..., dn-1)
custom(d0, d1, ..., dn-1, x)Trueuser(d0, d1, ..., dn-1)

The content of Buffer object can be converted to/from Numpy array usingBuffer.copy_from_array(numpy_array) andBuffer.to_array() instance methods.

Variables or Buffers of Structs

If you have a variable or buffer of C structs in device code, you can still use NumPy arrays in Python host code.Canonical way to do it is to define a Python class corresponding to the C struct. In the class, define a customdtype and__array__ method. The dtype must match with the memory layout of the C struct.The__array__ method must create a NumPy array with the custom dtype, fill the values according to the contentsof the class instance, and return the array. NumPy will use__array__ method to cast an object to NumPy array.When assigning the object to the variable, wrap it with numpy.array function.When creating a Buffer from an array of objects, make it a NumPy array andset drop_last_dim to True since the objects themselves will be NumPy arrays with custom dtypes.

Entry Points

EntryPoint class encapsulates entry point concept in OptiX.EntryPoint objects are created by passing a raygeneration program and an optional exception program; and they can be launched later with given sizes. You don't needto set entry point counts or keep track of them to launch them. Just keep EntryPoints in variables and launch themusingEntryPoint.launch() instance method.

Installation

Prerequisites

  • Install the necessary libraries: Python dev package, Boost.Python, setuptools.For Ubuntu, the install command will look like this:

      sudo apt-get install -y build-essential python-dev python-setuptools python3-dev python3-setuptools libboost-python-dev
  • CUDA andOptiX SDK's must be installed before installing PyOptiX.

  • nvcc must be inPATH.

  • CUDA,OptiX, andBoost.Python library paths must be in eitherldconfig orLD_LIBRARY_PATH.

Using pip

pip install pyoptix

From source

git clone https://github.com/ozen/PyOptiX.gitcd pyoptixpython setup.py install

pyoptix.conf file

pyoptix.conf file is explained in Concepts > PTX Generation section. pyoptix.Compiler cannot work out of the boxif pyoptix.conf file creation fails during installation.

Please note that pip creates wheel distribution of the package and caches it during installation.Subsequent pip install commands for the same version of the package will use the cached wheel,therefore setup.py script won't be executed and pyoptix.conf file won't be created.When you want to prevent this you can use --no-binary flag:

pip install pyoptix --no-binary pyoptix

Using the Docker Image

  1. Check outoptix-docker to build an OptiX-enabled Docker image.

  2. Build a PyOptiX image on top of the OptiX image using the Dockerfile provided in the source directory of PyOptiX. You can specify the name of the OptiX-enabled Docker image using --build-args command. The default name isoptix.

     cd PyOptiX docker build -t pyoptix --build-args OPTIX_IMAGE=optix .
  3. Run an example in a docker container using the image. Usenvidia-docker to be able to use the GPU in the container.Following command will also make the container able to access host machine's X11 server, so you will be able to see the result window.

     docker run --runtime=nvidia -it --rm \     --volume=/etc/group:/etc/group:ro \     --volume=/etc/passwd:/etc/passwd:ro \     --volume=/etc/shadow:/etc/shadow:ro \     --volume=/etc/sudoers:/etc/sudoers:ro \     --volume=/etc/sudoers.d:/etc/sudoers.d:ro \     --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \     --user=$(id -u) \     --env="DISPLAY" \     pyoptix python3 /usr/src/PyOptiX/examples/hello/hello.py

About

Python wrapper for NVIDIA OptiX Ray Tracing Engine

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp