diff --git a/docs/index.rst b/docs/index.rst index b1994dc9..e13cf0ce 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -56,6 +56,7 @@ If you use SlangPy in a research project leading to a publication, please cite t :caption: Basics src/basics/firstfunctions + src/basics/returntype src/basics/buffers src/basics/textures src/basics/nested diff --git a/docs/src/basics/returntype.rst b/docs/src/basics/returntype.rst new file mode 100644 index 00000000..a3a7d0a4 --- /dev/null +++ b/docs/src/basics/returntype.rst @@ -0,0 +1,45 @@ +Return Types +============ + +Slangpy can generate different types of container to hold the returned results of a Slang function. This is convenient for getting results in a preferred container type, such as a numpy array, texture, or tensor. + +Let's start by reusing the example from :ref:`firstfunctions`. + +Shader: + +.. code-block:: + + // example.slang + + // A simple function that adds two numbers together + float add(float a, float b) + { + return a + b; + } + +In the original :ref:`firstfunctions` python example, we returned the result in a numpy array. Let's return it as a texture instead: + +.. code-block:: python + + ## main.py + + # ... initialization here ... + + # Create a couple of buffers with 128x128 random floats + a = np.random.rand(128, 128).astype(np.float32) + b = np.random.rand(128, 128).astype(np.float32) + + # Call our function and ask for a texture back + result = module.add(a, b, _result='texture') + + # Print the first 5x5 values + print(result.to_numpy()[:5, :5]) + + # Display the result using tev + spy.tev.show(result, name='add random') + +Here we use ``_result`` to specify that we want the result to be a texture. + +The ``_result`` can be ``'numpy'``, ``'texture'``, or ``'tensor'``. You can also use ``_result`` to specify the type directly, like ``numpy.ndarray``, ``slangpy.Texture``, or ``slangpy.Tensor``. Or you can reuse an existing variable of one of those types by passing it directly. + +You'll see more examples using ``_result`` in the rest of this documentation! diff --git a/slangpy/core/function.py b/slangpy/core/function.py index ab6da48d..105657ef 100644 --- a/slangpy/core/function.py +++ b/slangpy/core/function.py @@ -164,6 +164,10 @@ def return_type(self, return_type: Union[type, str]): from slangpy.types import Tensor return_type = Tensor + elif return_type == "texture": + from slangpy import Texture + + return_type = Texture else: raise ValueError(f"Unknown return type '{return_type}'") return FunctionNodeReturnType(self, return_type) diff --git a/slangpy/tests/slangpy_tests/test_textures.py b/slangpy/tests/slangpy_tests/test_textures.py index f83ba21f..ac6d57c8 100644 --- a/slangpy/tests/slangpy_tests/test_textures.py +++ b/slangpy/tests/slangpy_tests/test_textures.py @@ -2,6 +2,7 @@ import numpy as np import pytest from slangpy import TextureDesc, TextureUsage +from typing import Union from . import helpers from slangpy import InstanceBuffer, Module @@ -274,7 +275,7 @@ def test_read_write_texture_with_resource_views( [TextureType.texture_1d, TextureType.texture_2d, TextureType.texture_3d], ) @pytest.mark.parametrize("slices", [1, 4]) -@pytest.mark.parametrize("mips", [1]) +@pytest.mark.parametrize("mips", [ALL_MIPS, 1]) @pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) def test_copy_value(device_type: DeviceType, slices: int, mips: int, type: TextureType): m = load_test_module(device_type) @@ -432,13 +433,13 @@ def test_texture_3d_shapes(device_type: DeviceType, shape: tuple[int, ...]): assert np.allclose(copied, tex_data) -@pytest.mark.parametrize( - "texel_name", ["uint8_t", "uint16_t", "int8_t", "int16_t", "float", "half", "uint"] -) -@pytest.mark.parametrize("dims", [1, 2, 3]) -@pytest.mark.parametrize("channels", [1, 2, 4]) -@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) -def test_texture_return_value(device_type: DeviceType, texel_name: str, dims: int, channels: int): +def texture_return_value_impl( + device_type: DeviceType, + texel_name: str, + dims: int, + channels: int, + return_type: Union[str, type], +): if texel_name in ("uint8_t", "int8_t") and device_type == DeviceType.d3d12: pytest.skip("8-bit types not supported by DXC") @@ -460,7 +461,7 @@ def test_texture_return_value(device_type: DeviceType, texel_name: str, dims: in buffer = NDBuffer(m.device, dtype, shape=shape) buffer.copy_from_numpy(data) - result = m.passthru.map(buffer.dtype)(buffer, _result=Texture) + result = m.passthru.map(buffer.dtype)(buffer, _result=return_type) assert isinstance(result, Texture) if dims == 1: @@ -489,5 +490,27 @@ def test_texture_return_value(device_type: DeviceType, texel_name: str, dims: in assert np.allclose(result_np, data.squeeze()) +@pytest.mark.parametrize( + "texel_name", ["uint8_t", "uint16_t", "int8_t", "int16_t", "float", "half", "uint"] +) +@pytest.mark.parametrize("dims", [1, 2, 3]) +@pytest.mark.parametrize("channels", [1, 2, 4]) +@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) +def test_texture_return_value(device_type: DeviceType, texel_name: str, dims: int, channels: int): + texture_return_value_impl(device_type, texel_name, dims, channels, Texture) + + +# This case checks for when the return type is the string "texture". +# This checks a subset of the "test_texture_return_value" parameters. +@pytest.mark.parametrize("texel_name", ["float"]) +@pytest.mark.parametrize("dims", [1, 2, 3]) +@pytest.mark.parametrize("channels", [4]) +@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) +def test_texture_return_value_str( + device_type: DeviceType, texel_name: str, dims: int, channels: int +): + texture_return_value_impl(device_type, texel_name, dims, channels, "texture") + + if __name__ == "__main__": pytest.main([__file__, "-v", "-s"])