Skip to content

Commit 5da9453

Browse files
authored
Update "neural graphics in an afternoon" blog for post-SGL
Updated links to example code to reflect move to slangpy-samples repository, and updated the code to use the newest SlangPy (after the removal of SGL.)
1 parent 9542774 commit 5da9453

File tree

1 file changed

+34
-30
lines changed

1 file changed

+34
-30
lines changed

_posts/2025-04-04-neural-gfx-in-an-afternoon.md

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,34 @@ Slang makes this entire process much easier, because it can automatically calcul
4848

4949
## The Code
5050

51-
Let’s take a look at what it looks like to do this in the code. I’ll first go through a simplified version of the 2D Gaussian splatting example, so it’s very clear how the mechanism works. You can find this example in the SlangPy repository [here](https://github.com/shader-slang/slangpy/tree/main/examples/simplified-splatting). First, we’ll check out the Python side of things. With SlangPy, this code is pretty succinct.
51+
Let’s take a look at what it looks like to do this in the code. I’ll first go through a simplified version of the 2D Gaussian splatting example, so it’s very clear how the mechanism works. You can find this example in the SlangPy repository [here](https://github.com/shader-slang/slangpy-samples/tree/main/examples/simplified-splatting). First, we’ll check out the Python side of things. With SlangPy, this code is pretty succinct.
5252

5353
```python
54-
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
54+
# SPDX-License-Identifier: Apache-2.0
5555

5656
import slangpy as spy
57-
import sgl
5857
import pathlib
5958
import imageio
6059
import numpy as np
6160

62-
# Create an SGL device, which will handle setup and invocation of the Slang
61+
# Create a device, which will handle setup and invocation of the Slang
6362
# compiler for us. We give it both the slangpy PATH and the local include
6463
# PATH so that it can find Slang shader files
65-
device = sgl.Device(compiler_options={
66-
"include_paths": [
67-
spy.SHADER_PATH,
68-
pathlib.Path(__file__).parent.absolute(),
69-
],
70-
})
64+
device = spy.Device(
65+
compiler_options={
66+
"include_paths": [
67+
spy.SHADER_PATH,
68+
pathlib.Path(__file__).parent.absolute(),
69+
],
70+
}
71+
)
7172

7273
# Load our Slang module -- we'll take a look at this in just a moment
7374
module = spy.Module.load_from_file(device, "simplediffsplatting2d.slang")
7475

7576
# Create a buffer to store Gaussian blobs. We're going to make a very small one,
7677
# because right now this code is not very efficient, and will take a while to run.
77-
# For now, we are going to create 200 blobs, and each blob will be comprised of 9
78+
# For now, we are going to create 200 blobs, and each blob will be comprised of 9
7879
# floats:
7980
# blob center x and y (2 floats)
8081
# sigma (a 2x2 covariance matrix - 4 floats)
@@ -84,26 +85,28 @@ FLOATS_PER_BLOB = 9
8485
# SlangPy lets us create a Tensor and initialize it easily using numpy to generate
8586
# random values. This Tensor includes storage for gradients, because we call .with_grads()
8687
# on the created spy.Tensor.
87-
blobs = spy.Tensor.numpy(device, np.random.rand(
88-
NUM_BLOBS * FLOATS_PER_BLOB).astype(np.float32)).with_grads()
88+
blobs = spy.Tensor.from_numpy(
89+
device, np.random.rand(NUM_BLOBS * FLOATS_PER_BLOB).astype(np.float32)
90+
).with_grads()
8991

9092
# Load our target image from a file, using the imageio package,
9193
# and store its width and height in W, H
9294
image = imageio.imread("./jeep.jpg")
9395
W = image.shape[0]
9496
H = image.shape[1]
9597

96-
# Convert the image from RGB_u8 to RGBA_f32 -- we're going
98+
# Convert the image from RGB_u8 to RGBA_f32 -- we're going
9799
# to be using texture values during derivative propagation,
98-
# so we need to be dealing with floats here.
100+
# so we need to be dealing with floats here.
99101
image = (image / 256.0).astype(np.float32)
100102
image = np.concatenate([image, np.ones((W, H, 1), dtype=np.float32)], axis=-1)
101103
input_image = device.create_texture(
102104
data=image,
103105
width=W,
104106
height=H,
105-
format=sgl.Format.rgba32_float,
106-
usage=sgl.ResourceUsage.shader_resource)
107+
format=spy.Format.rgba32_float,
108+
usage=spy.TextureUsage.shader_resource,
109+
)
107110

108111
# Create a per_pixel_loss Tensor to hold the calculated loss, and create gradient storage
109112
per_pixel_loss = spy.Tensor.empty(device, dtype=module.float4, shape=(W, H))
@@ -124,26 +127,27 @@ adam_second_moment = spy.Tensor.zeros_like(blobs)
124127
current_render = device.create_texture(
125128
width=W,
126129
height=H,
127-
format=sgl.Format.rgba32_float,
128-
usage=sgl.ResourceUsage.shader_resource | sgl.ResourceUsage.unordered_access)
130+
format=spy.Format.rgba32_float,
131+
usage=spy.TextureUsage.shader_resource | spy.TextureUsage.unordered_access,
132+
)
129133

130134
iterations = 10000
131135
for iter in range(iterations):
132136
# Back-propagage the unit per-pixel loss with auto-diff.
133-
module.perPixelLoss.bwds(per_pixel_loss,
134-
spy.grid(shape=(input_image.width,input_image.height)),
135-
blobs, input_image)
137+
module.perPixelLoss.bwds(
138+
per_pixel_loss, spy.grid(shape=(input_image.width, input_image.height)), blobs, input_image
139+
)
136140

137141
# Update the parameters using the Adam algorithm
138142
module.adamUpdate(blobs, blobs.grad_out, adam_first_moment, adam_second_moment)
139143

140144
# Every 50 iterations, render the blobs out to a texture, and hand it off to tev
141145
# so that you can visualize the iteration towards ideal
142146
if iter % 50 == 0:
143-
module.renderBlobsToTexture(current_render,
144-
blobs,
145-
spy.grid(shape=(input_image.width,input_image.height)))
146-
sgl.tev.show_async(current_render, name=f"optimization_{(iter // 50):03d}")
147+
module.renderBlobsToTexture(
148+
current_render, blobs, spy.grid(shape=(input_image.width, input_image.height))
149+
)
150+
spy.tev.show_async(current_render, name=f"optimization_{(iter // 50):03d}")
147151

148152
```
149153

@@ -178,10 +182,10 @@ This line calls into a Slang function in our module which provides an [optimized
178182
# Every 50 iterations, render the blobs out to a texture, and hand it off to tev
179183
# so that you can visualize the iteration towards ideal
180184
if iter % 50 == 0:
181-
module.renderBlobsToTexture(current_render,
182-
blobs,
183-
spy.grid(shape=(input_image.width,input_image.height)))
184-
sgl.tev.show_async(current_render, name=f"optimization_{(iter // 50):03d}")
185+
module.renderBlobsToTexture(
186+
current_render, blobs, spy.grid(shape=(input_image.width, input_image.height))
187+
)
188+
spy.tev.show_async(current_render, name=f"optimization_{(iter // 50):03d}")
185189
```
186190

187191
And then finally, we use one last function in our Slang module to render the results of our blobs out to a texture, instead of just keeping them in memory, so that we can visualize the results of the iterations as we go on. We’re doing 10 thousand iterations, though, so looking at every iteration might be overkill, so we’ll only render out every 50th iteration.

0 commit comments

Comments
 (0)