1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package render_ir_tests
import ir "../../render_ir"
import "core:testing"
has_access :: proc(
accesses: []ir.Resource_Access,
command: ir.Command_Handle,
resource: ir.Resource_Handle,
kind: ir.Resource_Access_Kind,
) -> bool {
for access in accesses {
if access.command == command && access.resource == resource && access.kind == kind {
return true
}
}
return false
}
has_metadata_gap :: proc(accesses: []ir.Resource_Access, command: ir.Command_Handle) -> bool {
for access in accesses {
if access.command == command && access.kind == .Access_Metadata_Missing {
return true
}
}
return false
}
make_sampled_material :: proc(
frame: ^ir.Frame_IR,
texture: ir.Resource_Handle,
sampler: ir.Resource_Handle,
) -> ir.Material_Handle {
layout := ir.make_descriptor_set_layout("set0")
_ = ir.descriptor_layout_add_binding(&layout, 0, .Combined_Image_Sampler, 1, {.Fragment})
layout_handle := ir.add_descriptor_set_layout(frame, layout)
set := ir.make_descriptor_set("material", layout_handle)
_ = ir.descriptor_set_bind_texture_sampler(&set, 0, texture, sampler)
set_handle := ir.add_descriptor_set(frame, set)
return ir.add_material(frame, {name = "mat", descriptor_set = set_handle})
}
@(test)
test_resource_access_analyzer_classifies_known_resources :: proc(t: ^testing.T) {
frame := ir.init_frame_ir()
defer ir.destroy_frame_ir(&frame)
color := ir.add_texture(&frame, "color", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled})
sampled := ir.add_texture(&frame, "sampled", 16, 16, .R8G8B8A8_UNORM, {.Sampled})
sampler := ir.add_sampler(&frame, "sampler", {})
vertex := ir.add_buffer(&frame, "vertices", 256, {.Vertex}, {.Device_Local})
instance := ir.add_buffer(&frame, "instances", 128, {.Vertex}, {.Device_Local})
indirect := ir.add_buffer(&frame, "indirect", 64, {.Indirect_Argument}, {.Device_Local})
storage := ir.add_buffer(&frame, "storage", 64, {.Storage}, {.Device_Local})
pass_desc := ir.Pass_Desc{kind = .Render, name = "main"}
_ = ir.pass_add_color_target(&pass_desc, ir.make_color_target(color, .R8G8B8A8_UNORM))
pass := ir.add_pass(&frame, pass_desc)
compute_pass := ir.add_pass(&frame, {kind = .Compute, name = "compute"})
material := make_sampled_material(&frame, sampled, sampler)
draw := ir.add_draw(
&frame,
{
pass = pass,
pipeline = ir.Pipeline_Handle(1),
vertex_count = 6,
packet = {
material = material,
geometry = {kind = .Vertex_Stream, resource = vertex, vertex_count = 6},
instances = {resource = instance, indirect = indirect, stride = 16, count = 1},
},
},
)
dispatch_desc := ir.Dispatch_Command {
pass = compute_pass,
pipeline = ir.Pipeline_Handle(2),
groups = {1, 1, 1},
storage_binding_count = 1,
}
dispatch_desc.storage_bindings[0] = {binding = 0, type = .Storage_Buffer, resource = storage}
_ = ir.add_dispatch(&frame, dispatch_desc)
accesses := ir.collect_resource_accesses(&frame)
defer ir.destroy_resource_accesses(&accesses)
testing.expect(t, has_access(accesses[:], draw, color, .Color_Attachment_Write))
testing.expect(t, has_access(accesses[:], draw, sampled, .Sampled_Texture_Read))
testing.expect(t, has_access(accesses[:], draw, vertex, .Vertex_Buffer_Read))
testing.expect(t, has_access(accesses[:], draw, instance, .Instance_Buffer_Read))
testing.expect(t, has_access(accesses[:], draw, indirect, .Indirect_Argument_Read))
testing.expect(t, has_access(accesses[:], ir.Command_Handle(2), storage, .Storage_Buffer_Read_Write))
}
@(test)
test_resource_access_analyzer_marks_runtime_texture_metadata_gap :: proc(t: ^testing.T) {
frame := ir.init_frame_ir()
defer ir.destroy_frame_ir(&frame)
pass := ir.add_pass(&frame, {kind = .Render, name = "main"})
material := ir.add_material(&frame, {name = "runtime-texture", texture_id = 9})
draw := ir.add_draw(
&frame,
{
pass = pass,
pipeline = ir.Pipeline_Handle(1),
vertex_count = 3,
packet = {material = material},
},
)
accesses := ir.collect_resource_accesses(&frame)
defer ir.destroy_resource_accesses(&accesses)
testing.expect(t, has_metadata_gap(accesses[:], draw))
}
@(test)
test_resource_access_validation_rejects_known_same_pass_feedback :: proc(t: ^testing.T) {
frame := ir.init_frame_ir()
defer ir.destroy_frame_ir(&frame)
color := ir.add_texture(&frame, "color", 16, 16, .R8G8B8A8_UNORM, {.Color_Attachment, .Sampled})
sampler := ir.add_sampler(&frame, "sampler", {})
pass_desc := ir.Pass_Desc{kind = .Render, name = "main"}
_ = ir.pass_add_color_target(&pass_desc, ir.make_color_target(color, .R8G8B8A8_UNORM))
pass := ir.add_pass(&frame, pass_desc)
material := make_sampled_material(&frame, color, sampler)
draw := ir.add_draw(
&frame,
{
pass = pass,
pipeline = ir.Pipeline_Handle(1),
vertex_count = 3,
packet = {material = material},
},
)
diagnostics := ir.init_diagnostics()
defer ir.destroy_diagnostics(&diagnostics)
testing.expect(t, !ir.validate_resource_accesses(&frame, &diagnostics))
testing.expect_value(t, len(diagnostics.items), 1)
testing.expect_value(t, diagnostics.items[0].command, draw)
testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Resource_Feedback)
}
@(test)
test_resource_access_validation_rejects_offscreen_after_present :: proc(t: ^testing.T) {
frame := ir.init_frame_ir()
defer ir.destroy_frame_ir(&frame)
present_pass := ir.add_pass(&frame, {kind = .Render, name = "present"})
offscreen_pass := ir.add_pass(&frame, {kind = .Render, name = "offscreen"})
present := ir.add_target(&frame, {kind = .Present, name = "present", width = 16, height = 16, pass = present_pass})
offscreen := ir.add_target(&frame, {kind = .Offscreen, name = "offscreen", width = 16, height = 16, pass = offscreen_pass})
_ = ir.add_draw(
&frame,
{
pass = ir.INVALID_PASS,
pipeline = ir.Pipeline_Handle(1),
vertex_count = 3,
packet = {target = present},
},
)
offscreen_draw := ir.add_draw(
&frame,
{
pass = offscreen_pass,
pipeline = ir.Pipeline_Handle(1),
vertex_count = 3,
packet = {target = offscreen},
},
)
diagnostics := ir.init_diagnostics()
defer ir.destroy_diagnostics(&diagnostics)
testing.expect(t, !ir.validate_resource_accesses(&frame, &diagnostics))
testing.expect_value(t, len(diagnostics.items), 1)
testing.expect_value(t, diagnostics.items[0].command, offscreen_draw)
testing.expect_value(t, diagnostics.items[0].code, ir.Diagnostic_Code.Invalid_Order)
}