Skip to content

Commit c7cd082

Browse files
committed
get rid of offset allocation
1 parent e22a231 commit c7cd082

File tree

9 files changed

+232
-187
lines changed

9 files changed

+232
-187
lines changed

docs/code/caustic_glass.jl

Lines changed: 81 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,96 @@
11
using GeometryBasics
22
using LinearAlgebra
3-
using Trace
3+
using Trace, FileIO, MeshIO
44

5-
function render()
6-
glass = Trace.GlassMaterial(
7-
Trace.ConstantTexture(Trace.RGBSpectrum(1f0)),
8-
Trace.ConstantTexture(Trace.RGBSpectrum(1f0)),
9-
Trace.ConstantTexture(0f0),
10-
Trace.ConstantTexture(0f0),
11-
Trace.ConstantTexture(1.25f0),
12-
true,
13-
)
14-
plastic = Trace.PlasticMaterial(
15-
Trace.ConstantTexture(Trace.RGBSpectrum(0.6399999857f0, 0.6399999857f0, 0.6399999857f0)),
16-
Trace.ConstantTexture(Trace.RGBSpectrum(0.1000000015f0, 0.1000000015f0, 0.1000000015f0)),
17-
Trace.ConstantTexture(0.010408001f0),
18-
true,
19-
)
5+
glass = Trace.GlassMaterial(
6+
Trace.ConstantTexture(Trace.RGBSpectrum(1f0)),
7+
Trace.ConstantTexture(Trace.RGBSpectrum(1f0)),
8+
Trace.ConstantTexture(0f0),
9+
Trace.ConstantTexture(0f0),
10+
Trace.ConstantTexture(1.25f0),
11+
true,
12+
)
13+
plastic = Trace.PlasticMaterial(
14+
Trace.ConstantTexture(Trace.RGBSpectrum(0.6399999857f0, 0.6399999857f0, 0.6399999857f0)),
15+
Trace.ConstantTexture(Trace.RGBSpectrum(0.1000000015f0, 0.1000000015f0, 0.1000000015f0)),
16+
Trace.ConstantTexture(0.010408001f0),
17+
true,
18+
)
2019

21-
model = "./scenes/models/caustic-glass.ply"
22-
triangle_meshes, triangles = Trace.load_triangle_mesh(
23-
model, Trace.ShapeCore(Trace.translate(Vec3f(5, -1.49, -100)), false),
24-
)
25-
floor_triangles = Trace.create_triangle_mesh(
26-
Trace.ShapeCore(Trace.translate(Vec3f(-10, 0, -87)), false),
27-
2, UInt32[1, 2, 3, 1, 4, 3],
28-
4,
29-
[
30-
Point3f(0, 0, 0), Point3f(0, 0, -30),
31-
Point3f(30, 0, -30), Point3f(30, 0, 0),
32-
],
33-
[
34-
Trace.Normal3f(0, 1, 0), Trace.Normal3f(0, 1, 0),
35-
Trace.Normal3f(0, 1, 0), Trace.Normal3f(0, 1, 0),
36-
],
37-
)
20+
model = load(joinpath(@__DIR__, "..", "src", "assets", "models", "caustic-glass.ply"))
3821

39-
primitives = Vector{Trace.GeometricPrimitive}(undef, 0)
40-
for t in triangles
41-
push!(primitives, Trace.GeometricPrimitive(t, glass))
42-
end
43-
for t in floor_triangles
44-
push!(primitives, Trace.GeometricPrimitive(t, plastic))
45-
end
4622

47-
bvh = Trace.BVHAccel(primitives, 1)
23+
triangles = Trace.create_triangle_mesh(
24+
model, Trace.ShapeCore(Trace.translate(Vec3f(5, -1.49, -100)), false),
25+
)
4826

49-
from, to = Point3f(0, 2, 0), Point3f(-5, 0, 5)
50-
cone_angle, cone_δ_angle = 30f0, 10f0
51-
dir = normalize(Vec3f(to - from))
52-
dir, du, dv = Trace.coordinate_system(dir)
27+
floor_triangles = Trace.create_triangle_mesh(
28+
Trace.ShapeCore(Trace.translate(Vec3f(-10, 0, -87)), false),
29+
UInt32[1, 2, 3, 1, 4, 3],
30+
[
31+
Point3f(0, 0, 0), Point3f(0, 0, -30),
32+
Point3f(30, 0, -30), Point3f(30, 0, 0),
33+
],
34+
[
35+
Trace.Normal3f(0, 1, 0), Trace.Normal3f(0, 1, 0),
36+
Trace.Normal3f(0, 1, 0), Trace.Normal3f(0, 1, 0),
37+
],
38+
)
5339

54-
dir_to_z = Trace.Transformation(transpose(Mat4f(
55-
du[1], du[2], du[3], 0f0,
56-
dv[1], dv[2], dv[3], 0f0,
57-
dir[1], dir[2], dir[3], 0f0,
58-
0f0, 0f0, 0f0, 1f0,
59-
)))
60-
light_to_world = (
61-
Trace.translate(Vec3f(4.5, 0, -101))
62-
* Trace.translate(Vec3f(from))
63-
* inv(dir_to_z)
64-
)
40+
primitives = Trace.GeometricPrimitive[]
41+
for t in triangles
42+
push!(primitives, Trace.GeometricPrimitive(t, glass))
43+
end
44+
for t in floor_triangles
45+
push!(primitives, Trace.GeometricPrimitive(t, plastic))
46+
end
6547

66-
lights = [
67-
Trace.SpotLight(
68-
light_to_world, Trace.RGBSpectrum(60f0),
69-
cone_angle, cone_angle - cone_δ_angle,
70-
),
71-
]
48+
bvh = Trace.BVHAccel(primitives, 1);
7249

73-
scene = Trace.Scene(lights, bvh)
50+
from, to = Point3f(0, 2, 0), Point3f(-5, 0, 5)
51+
cone_angle, cone_δ_angle = 30f0, 10f0
52+
dir = normalize(Vec3f(to - from))
53+
dir, du, dv = Trace.coordinate_system(dir)
7454

75-
resolution = Point2f(256)
76-
n_samples = 8
77-
ray_depth = 5
55+
dir_to_z = Trace.Transformation(transpose(Mat4f(
56+
du[1], du[2], du[3], 0f0,
57+
dv[1], dv[2], dv[3], 0f0,
58+
dir[1], dir[2], dir[3], 0f0,
59+
0f0, 0f0, 0f0, 1f0,
60+
)))
61+
light_to_world = (
62+
Trace.translate(Vec3f(4.5, 0, -101))
63+
* Trace.translate(Vec3f(from))
64+
* inv(dir_to_z)
65+
)
7866

79-
look_point = Point3f(-3, 0, -91)
80-
screen = Trace.Bounds2(Point2f(-1f0), Point2f(1f0))
81-
filter = Trace.LanczosSincFilter(Point2f(1f0), 3f0)
67+
lights = [
68+
Trace.SpotLight(
69+
light_to_world, Trace.RGBSpectrum(60f0),
70+
cone_angle, cone_angle - cone_δ_angle,
71+
),
72+
]
8273

83-
ir = Int64.(resolution)
84-
film = Trace.Film(
85-
resolution, Trace.Bounds2(Point2f(0), Point2f(1)),
86-
filter, 1f0, 1f0,
87-
"./scenes/caustics-sppm-$(ir[1])x$(ir[2]).png",
88-
)
89-
camera = Trace.PerspectiveCamera(
90-
Trace.look_at(Point3f(0, 150, 150), look_point, Vec3f(0, 1, 0)),
91-
screen, 0f0, 1f0, 0f0, 1f6, 90f0, film,
92-
)
74+
scene = Trace.Scene(lights, bvh)
9375

94-
integrator = Trace.SPPMIntegrator(camera, 0.075f0, ray_depth, 100, -1)
95-
integrator(scene)
96-
end
76+
resolution = Point2f(1024)
77+
n_samples = 8
78+
ray_depth = 5
79+
80+
look_point = Point3f(-3, 0, -91)
81+
screen = Trace.Bounds2(Point2f(-1f0), Point2f(1f0))
82+
filter = Trace.LanczosSincFilter(Point2f(1f0), 3f0)
83+
84+
ir = Int64.(resolution)
85+
film = Trace.Film(
86+
resolution, Trace.Bounds2(Point2f(0), Point2f(1)),
87+
filter, 1f0, 1f0,
88+
"./scenes/caustics-sppm-$(ir[1])x$(ir[2]).png",
89+
)
90+
camera = Trace.PerspectiveCamera(
91+
Trace.look_at(Point3f(0, 150, 150), look_point, Vec3f(0, 1, 0)),
92+
screen, 0f0, 1f0, 0f0, 1f6, 90f0, film,
93+
)
9794

98-
render()
95+
integrator = Trace.SPPMIntegrator(camera, 0.075f0, ray_depth, 10)
96+
integrator(scene)

src/film.jl

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,20 @@ struct FilmTile{Pixels<:AbstractMatrix{<:FilmTilePixel}}
9999
bounds::Bounds2
100100
filter_radius::Point2f
101101
inv_filter_radius::Point2f
102-
filter_table::Matrix{Float32}
103102
filter_table_width::Int32
104103
pixels::Pixels
105104

106105
function FilmTile(
107106
bounds::Bounds2, filter_radius::Point2f,
108-
filter_table::Matrix{Float32}, filter_table_width::Int32,
107+
filter_table_width::Int32,
109108
)
110109
tile_res = (Int32.(inclusive_sides(bounds)))
111-
pixels = StructArray{FilmTilePixel{RGBSpectrum}}(undef, tile_res[2], tile_res[1])
112-
pixels.contrib_sum .= (RGBSpectrum(),)
113-
pixels.filter_weight_sum .= 0.0f0
110+
contrib_sum = fill(RGBSpectrum(), tile_res[2], tile_res[1])
111+
filter_weight_sum = fill(0.0f0, tile_res[2], tile_res[1])
112+
pixels = StructArray{FilmTilePixel{RGBSpectrum}}((contrib_sum, filter_weight_sum))
114113
new{typeof(pixels)}(
115114
bounds, filter_radius, 1f0 ./ filter_radius,
116-
filter_table, filter_table_width,
115+
filter_table_width,
117116
pixels,
118117
)
119118
end
@@ -126,7 +125,30 @@ function FilmTile(f::Film, sample_bounds::Bounds2)
126125
p0 = ceil.(sample_bounds.p_min .- 0.5f0 .- f.filter.radius)
127126
p1 = floor.(sample_bounds.p_max .- 0.5f0 .+ f.filter.radius) .+ 1f0
128127
tile_bounds = Bounds2(p0, p1) f.crop_bounds
129-
FilmTile(tile_bounds, f.filter.radius, f.filter_table, f.filter_table_width)
128+
FilmTile(tile_bounds, f.filter.radius, f.filter_table_width)
129+
end
130+
131+
function filter_offset!(offsets, start, stop, discrete_point, inv_filter_radius, filter_table_width)
132+
@inbounds for (i, x) in enumerate(Int(start):Int(stop))
133+
fx = abs((x - discrete_point[1]) * inv_filter_radius * filter_table_width)
134+
offsets[i] = clamp(ceil(fx), 1, filter_table_width) # TODO is clipping ok?
135+
end
136+
end
137+
138+
@inline function filter_offset(x, discrete_point, inv_filter_radius, filter_table_width)
139+
fx = abs((x - discrete_point[1]) * inv_filter_radius * filter_table_width)
140+
return clamp(ceil(Int32, fx), Int32(1), Int32(filter_table_width)) # TODO is clipping ok?
141+
end
142+
143+
function filter_offsets(start, stop, discrete_point, inv_filter_radius, filter_table_width)::NTuple{8, Int32}
144+
range = Int32(start):Int32(stop)
145+
return ntuple(8) do i
146+
if i <= length(range)
147+
filter_offset(range[i], discrete_point, inv_filter_radius, filter_table_width)::Int32
148+
else
149+
Int32(0)
150+
end
151+
end
130152
end
131153

132154
"""
@@ -137,33 +159,26 @@ Add sample contribution to the film tile.
137159
And is relative to the film, not the film tile.
138160
"""
139161
function add_sample!(
140-
t::FilmTile, point::Point2f, spectrum::S,
162+
film::Film, t::FilmTile, point::Point2f, spectrum::S,
141163
sample_weight::Float32 = 1f0,
142164
) where S<:Spectrum
143165

144166
# Compute sample's raster bounds.
145167
discrete_point = point .- 0.5f0
146168
p0 = ceil.(Int32, discrete_point .- t.filter_radius)
147-
p1 = floor.(Int32, discrete_point .+ t.filter_radius) .+ 1
169+
p1 = floor.(Int32, discrete_point .+ t.filter_radius) .+ Int32(1)
148170
p0 = Int32.(max.(p0, max.(t.bounds.p_min, Point2{Int32}(1))))
149171
p1 = Int32.(min.(p1, t.bounds.p_max))
150172
# Precompute x & y filter offsets.
151-
offsets_x = Vector{Int32}(undef, p1[1] - p0[1] + 1)
152-
offsets_y = Vector{Int32}(undef, p1[2] - p0[2] + 1)
153-
@inbounds for (i, x) in enumerate(p0[1]:p1[1])
154-
fx = abs((x - discrete_point[1]) * t.inv_filter_radius[1] * t.filter_table_width)
155-
offsets_x[i] = clamp(ceil(fx), 1, t.filter_table_width) # TODO is clipping ok?
156-
end
157-
@inbounds for (i, y) in enumerate(p0[2]:p1[2])
158-
fy = abs((y - discrete_point[2]) * t.inv_filter_radius[2] * t.filter_table_width)
159-
offsets_y[i] = clamp(floor(fy), 1, t.filter_table_width)
160-
end
173+
offsets_x = filter_offsets(p0[1], p1[1], discrete_point, t.inv_filter_radius[1], t.filter_table_width)
174+
offsets_y = filter_offsets(p0[2], p1[2], discrete_point, t.inv_filter_radius[2], t.filter_table_width)
161175
# Loop over filter support & add sample to pixel array.
162176
pixels = t.pixels
163177
contrib_sum = pixels.contrib_sum
164178
filter_weight_sum = pixels.filter_weight_sum
165-
@inbounds for (j, y) in enumerate(p0[2]:p1[2]), (i, x) in enumerate(p0[1]:p1[1])
166-
w = t.filter_table[offsets_y[j], offsets_x[i]]
179+
filter_table = film.filter_table
180+
@inbounds for (j, y) in enumerate(Int(p0[2]):Int(p1[2])), (i, x) in enumerate(Int(p0[1]):Int(p1[1]))
181+
w = filter_table[offsets_y[j], offsets_x[i]]
167182
@real_assert sample_weight <= 1
168183
@real_assert w <= 1
169184
idx = get_pixel_index(t, Point2(x, y))
@@ -196,7 +211,7 @@ function merge_film_tile!(f::Film, ft::FilmTile)
196211
f_xyz = f.pixels.xyz
197212
f_filter_weight_sum = f.pixels.filter_weight_sum
198213
@inbounds for y in y_range, x in x_range
199-
pixel = Point2(x, y)
214+
pixel = Point2{Int32}(x, y)
200215
ft_idx = get_pixel_index(ft, pixel)
201216
f_idx = get_pixel_index(f, pixel)
202217
f_xyz[f_idx] += to_XYZ(ft_contrib_sum[ft_idx])
@@ -212,21 +227,28 @@ function set_image!(f::Film, spectrum::Matrix{S}) where {S<:Spectrum}
212227
end
213228

214229
function save(film::Film, splat_scale::Float32 = 1f0)
215-
image = Array{Float32}(undef, size(film.pixels)..., 3)
216-
for y in 1:size(film.pixels)[1], x in 1:size(film.pixels)[2]
217-
pixel = film.pixels[y, x]
218-
image[y, x, :] .= XYZ_to_RGB(pixel.xyz)
230+
image = film.framebuffer
231+
xyz = film.pixels.xyz
232+
filter_weight_sum = film.pixels.filter_weight_sum
233+
splat_xyz = film.pixels.splat_xyz
234+
235+
@inbounds for idx in eachindex(film.pixels)
236+
rgb = XYZ_to_RGB(xyz[idx])
219237
# Normalize pixel with weight sum.
220-
filter_weight_sum = pixel.filter_weight_sum
221-
if filter_weight_sum != 0
222-
inv_weight = 1f0 / filter_weight_sum
223-
image[y, x, :] .= max.(0f0, image[y, x, :] .* inv_weight)
238+
fws = filter_weight_sum[idx]
239+
if fws != 0
240+
inv_weight = 1.0f0 / fws
241+
rgb = max.(0.0f0, rgb .* inv_weight)
224242
end
225243
# Add splat value at pixel & scale.
226-
splat_rgb = XYZ_to_RGB(pixel.splat_xyz)
227-
image[y, x, :] .+= splat_scale .* splat_rgb
228-
image[y, x, :] .*= film.scale
244+
splat_rgb = XYZ_to_RGB(splat_xyz[idx])
245+
rgb = rgb .+ splat_scale .* splat_rgb
246+
rgb = rgb .* film.scale
247+
rgb = map(rgb) do c
248+
c = ifelse(isfinite(c), c, 0.0f0)
249+
return clamp(c, 0.0f0, 1.0f0)
250+
end
251+
image[idx] = RGB(rgb...)
229252
end
230-
clamp!(image, 0f0, 1f0) # TODO remap instead of clamping?
231-
FileIO.save(film.filename, image[end:-1:begin, :, :])
253+
FileIO.save(film.filename, @view image[end:-1:begin, :])
232254
end

src/integrators/sampler.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ end
1111
Render scene.
1212
"""
1313
function (i::SamplerIntegrator)(scene::Scene)
14+
1415
sample_bounds = get_sample_bounds(get_film(i.camera))
1516
sample_extent = diagonal(sample_bounds)
1617
tile_size = 16
@@ -23,6 +24,7 @@ function (i::SamplerIntegrator)(scene::Scene)
2324

2425
@info "Utilizing $(Threads.nthreads()) threads"
2526
mempools = [MemoryPool(round(Int, 2*16384)) for _ in 1:Threads.maxthreadid()]
27+
film = get_film(i.camera)
2628
Threads.@threads for k in 0:total_tiles
2729
x, y = k % width, k ÷ width
2830
tile = Point2f(x, y)
@@ -32,7 +34,7 @@ function (i::SamplerIntegrator)(scene::Scene)
3234
tb_max = min.(tb_min .+ (tile_size - 1), sample_bounds.p_max)
3335
tile_bounds = Bounds2(tb_min, tb_max)
3436

35-
film_tile = FilmTile(get_film(i.camera), tile_bounds)
37+
film_tile = FilmTile(film, tile_bounds)
3638
spp_sqr = 1f0 / Float32(t_sampler.samples_per_pixel)
3739
pool = mempools[Threads.threadid()]
3840
for pixel in tile_bounds
@@ -46,7 +48,7 @@ function (i::SamplerIntegrator)(scene::Scene)
4648
ω > 0.0f0 && (l = li(pool, i, ray, scene, 1))
4749
# TODO check l for invalid values
4850
isnan(l) && (l = RGBSpectrum(0f0))
49-
add_sample!(film_tile, camera_sample.film, l, ω)
51+
add_sample!(film, film_tile, camera_sample.film, l, ω)
5052
start_next_sample!(t_sampler)
5153
end
5254
end
@@ -108,6 +110,7 @@ function specular_reflect(
108110
) where I<:SamplerIntegrator
109111

110112
# Compute specular reflection direction `wi` and BSDF value.
113+
111114
wo = surface_intersect.core.wo
112115
type = BSDF_REFLECTION | BSDF_SPECULAR
113116
wi, f, pdf, sampled_type = sample_f(

src/materials/bsdf.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ function BSDF(si::SurfaceInteraction, sbdfs::Vararg{UberBxDF{S}, N}) where {S<:S
6161
end
6262

6363
function BSDF(si::SurfaceInteraction, η::Float32, sbdfs::Vararg{UberBxDF{S},N}) where {S<:Spectrum, N}
64+
6465
ng = si.core.n
6566
ns = si.shading.n
6667
ss = normalize(si.shading.∂p∂u)

src/primitive.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ function compute_scattering!(
3131
pool, p::GeometricPrimitive, si::SurfaceInteraction,
3232
allow_multiple_lobes::Bool, transport::UInt8,
3333
)
34+
@real_assert (si.core.n si.shading.n) 0f0
3435
if p.material.type !== NO_MATERIAL
3536
return p.material(pool, si, allow_multiple_lobes, transport)
3637
end
37-
# @real_assert (si.core.n ⋅ si.shading.n) ≥ 0f0
3838
return BSDF()
3939
end

0 commit comments

Comments
 (0)