Draw order and depth state: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: box (line 39), panel (line 48), flag (line 67), live (line 93), culling (line 101).
1/// Draw order and depth state: which surface is drawn first, whether it writes
2/// depth, how it is tested, and whether the back of a triangle is drawn.
3///
4/// Quoted by `draw_state.md` and shown whole in the Source tab.
5library;
7import 'dart:math' as math;
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13final class DrawStateDemo extends ShowcaseDemo {
14 int bucket = -1;
15 bool panelWritesDepth = false;
16 int testChoice = 0;
17 bool cullBackFaces = true;
19 static const List<String> _tests = <String>['less', 'always'];
21 late final Material _panel;
22 late final MeshNode _box;
25 void configureView(DemoContext context) {
26 context.orbit
27 ..distance = 8.0
28 ..yaw = 0.5
29 ..pitch = 0.2;
30 }
33 Scene build(DemoContext context) {
34 final Quaternion upright = Quaternion.axisAngle(
35 Vector3(1.0, 0.0, 0.0),
36 math.pi / 2,
37 );
39 _box = MeshNode(
40 DeviceMesh.upload(
41 context.device,
42 CuboidShape(size: Vector3(1.6, 1.6, 1.6)).build(),
43 ),
44 Material(name: 'box', baseColor: Vector4(0.25, 0.45, 0.85, 1.0)),
45 name: 'box',
46 );
48 _panel = Material(
49 name: 'panel',
50 baseColor: Vector4(0.95, 0.55, 0.2, 1.0),
51 drawBucket: bucket,
52 depthWrite: panelWritesDepth ? null : false,
53 depthCompare: testChoice == 0 ? null : CompareFunction.always,
54 );
55 final MeshNode panel =
56 MeshNode(
57 DeviceMesh.upload(
58 context.device,
59 const PlaneShape(width: 5, depth: 3).build(),
60 ),
61 _panel,
62 name: 'panel',
63 )
64 ..setRotation(upright)
65 ..setPosition(0.0, 0.0, 1.8);
67 final MeshNode flag =
68 MeshNode(
69 DeviceMesh.upload(
70 context.device,
71 const PlaneShape(width: 1.4, depth: 1.4).build(),
72 ),
73 Material(name: 'flag', baseColor: Vector4(0.8, 0.15, 0.2, 1.0)),
74 name: 'flag',
75 )
76 ..setRotation(
77 Quaternion.axisAngle(Vector3(1.0, 0.0, 0.0), -math.pi / 2),
78 )
79 ..setPosition(-2.9, 0.0, -0.5);
81 return Scene()
82 ..add(_box)
83 ..add(panel)
84 ..add(flag)
85 ..add(
86 LightNode(name: 'sun', intensity: 3.0)
87 ..setLocalForward(Vector3(-0.3, -0.5, -1.0)),
88 );
89 }
92 void update(DemoContext context, double dt) {
93 _panel
94 ..drawBucket = bucket
95 ..depthWrite = panelWritesDepth ? null : false
96 ..depthCompare = testChoice == 0 ? null : CompareFunction.always;
97 }
100 RenderSettings settings(DemoContext context) => RenderSettings(
101 backfaceCulling: cullBackFaces,
102 );
105 List<DemoControl> controls(DemoContext context) => <DemoControl>[
106 SliderControl(
107 'Panel draw bucket',
108 min: -1,
109 max: 1,
110 divisions: 2,
111 value: () => bucket.toDouble(),
112 onChanged: (double v) => bucket = v.round(),
113 format: (double v) => '${v.round()}',
114 ),
115 ToggleControl(
116 'Panel writes depth',
117 value: () => panelWritesDepth,
118 onChanged: (bool v) => panelWritesDepth = v,
119 ),
120 ChoiceControl(
121 'Panel depth test',
122 options: _tests,
123 index: () => testChoice,
124 onChanged: (int i) => testChoice = i,
125 ),
126 ToggleControl(
127 'Back-face culling',
128 value: () => cullBackFaces,
129 onChanged: (bool v) => cullBackFaces = v,
130 ),
131 ];
134 void verify(Scene scene, FrameResult frame) {
135 if (_panel.drawBucket >= _box.material.drawBucket) {
136 throw StateError('the panel is not ordered before the box');
137 }
138 if (_panel.depthWrite != false) {
139 throw StateError('the panel still writes depth');
140 }
141 final int sceneDraws = frame.passes
142 .where((FramePass pass) => pass.name == 'scene')
143 .fold(0, (int sum, FramePass pass) => sum + pass.drawCalls);
144 if (sceneDraws != scene.meshes.length) {
145 throw StateError(
146 'the scene pass drew $sceneDraws of ${scene.meshes.length} meshes',
147 );
148 }
149 }
150}