Skip to content

Commit

Permalink
feat!: Make resource creation be on demand to enable testing
Browse files Browse the repository at this point in the history
  • Loading branch information
luanpotter committed Dec 15, 2024
1 parent 726cb8b commit 4c5f726
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 120 deletions.
5 changes: 4 additions & 1 deletion packages/flame_3d/lib/src/resources/light/light.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ class Light extends Resource<void> {
Light({
required this.transform,
required this.source,
}) : super(null);
});

@override
void createResource() {}

void apply(int index, Shader shader) {
shader.setVector3('Light$index.position', transform.position);
Expand Down
29 changes: 8 additions & 21 deletions packages/flame_3d/lib/src/resources/material/material.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,28 @@ abstract class Material extends Resource<gpu.RenderPipeline> {
required Shader vertexShader,
required Shader fragmentShader,
}) : _vertexShader = vertexShader,
_fragmentShader = fragmentShader,
super(
gpu.gpuContext.createRenderPipeline(
vertexShader.compile().resource,
fragmentShader.compile().resource,
),
);
_fragmentShader = fragmentShader;

@override
gpu.RenderPipeline get resource {
var resource = super.resource;
if (_recreateResource) {
resource = super.resource = gpu.gpuContext.createRenderPipeline(
_vertexShader.compile().resource,
_fragmentShader.compile().resource,
);
_recreateResource = false;
}
return resource;
gpu.RenderPipeline createResource() {
return gpu.gpuContext.createRenderPipeline(
_vertexShader.compile().resource,
_fragmentShader.compile().resource,
);
}

bool _recreateResource = false;

Shader get vertexShader => _vertexShader;
Shader _vertexShader;
set vertexShader(Shader shader) {
_vertexShader = shader;
_recreateResource = true;
recreateResource = true;
}

Shader get fragmentShader => _fragmentShader;
Shader _fragmentShader;
set fragmentShader(Shader shader) {
_fragmentShader = shader;
_recreateResource = true;
recreateResource = true;
}

void bind(GraphicsDevice device) {}
Expand Down
7 changes: 4 additions & 3 deletions packages/flame_3d/lib/src/resources/mesh/mesh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import 'package:flame_3d/resources.dart';
/// {@endtemplate}
class Mesh extends Resource<void> {
/// {@macro mesh}
Mesh()
: _surfaces = [],
super(null);
Mesh() : _surfaces = [];

final List<Surface> _surfaces;
Aabb3? _aabb;
Expand All @@ -32,6 +30,9 @@ class Mesh extends Resource<void> {
}
}

@override
void createResource() {}

/// The total surface count of the mesh.
int get surfaceCount => _surfaces.length;

Expand Down
36 changes: 19 additions & 17 deletions packages/flame_3d/lib/src/resources/mesh/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Surface extends Resource<gpu.DeviceBuffer?> {
* If `true`, the normals will be calculated if they are not provided.
*/
bool calculateNormals = true,
}) : super(null) {
}) {
final normalizedVertices = _normalize(
vertices: vertices,
indices: indices,
Expand Down Expand Up @@ -61,25 +61,27 @@ class Surface extends Resource<gpu.DeviceBuffer?> {
int get indexCount => _indexCount;
late int _indexCount;

int? resourceSizeInByes;

@override
gpu.DeviceBuffer? get resource {
var resource = super.resource;
bool get recreateResource {
final sizeInBytes = _vertices.lengthInBytes + _indices.lengthInBytes;
if (resource?.sizeInBytes != sizeInBytes) {
// Store the device buffer in the resource parent.
resource = super.resource = gpu.gpuContext.createDeviceBuffer(
gpu.StorageMode.hostVisible,
sizeInBytes,
);
return resourceSizeInByes != sizeInBytes;
}

resource
?..overwrite(_vertices.asByteData())
..overwrite(
_indices.asByteData(),
destinationOffsetInBytes: _vertices.lengthInBytes,
);
}
return resource;
@override
gpu.DeviceBuffer? createResource() {
final sizeInBytes = _vertices.lengthInBytes + _indices.lengthInBytes;
resourceSizeInByes = sizeInBytes;
return gpu.gpuContext.createDeviceBuffer(
gpu.StorageMode.hostVisible,
sizeInBytes,
)
?..overwrite(_vertices.asByteData())
..overwrite(
_indices.asByteData(),
destinationOffsetInBytes: _vertices.lengthInBytes,
);
}

void _calculateAabb(List<Vertex> vertices) {
Expand Down
22 changes: 12 additions & 10 deletions packages/flame_3d/lib/src/resources/resource.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'package:meta/meta.dart';

// TODO(wolfenrain): in the long run it would be nice of we can make it
// automatically refer to same type of objects to prevent memory leaks

/// {@template resource}
/// A Resource is the base class for any resource typed classes. The primary
/// use case is to be a data container.
/// {@endtemplate}
class Resource<R> {
/// {@macro resource}
Resource(this._resource);
abstract class Resource<R> {
R? _resource;
bool recreateResource = true;

R createResource();

/// The resource data.
R get resource => _resource;
@protected
set resource(R resource) => _resource = resource;
R _resource;
R get resource {
if (recreateResource) {
_resource = createResource();
recreateResource = false;
}
return _resource!;
}
}
13 changes: 9 additions & 4 deletions packages/flame_3d/lib/src/resources/shader/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:flutter_gpu/gpu.dart' as gpu;
///
/// {@endtemplate}
class ShaderResource extends Resource<gpu.Shader> {
final gpu.Shader shader;

/// {@macro shader_resource}
factory ShaderResource.createFromAsset({
required String asset,
Expand All @@ -22,17 +24,20 @@ class ShaderResource extends Resource<gpu.Shader> {
if (shader == null) {
throw StateError('Shader "$shaderName" not found in library "$asset"');
}
return ShaderResource._(shader, slots: slots);
return ShaderResource._(shader: shader, slots: slots);
}

ShaderResource._(
super.resource, {
ShaderResource._({
required this.shader,
List<UniformSlot> slots = const [],
}) {
for (final slot in slots) {
slot.resource = resource.getUniformSlot(slot.name);
slot.uniformSlot = resource.getUniformSlot(slot.name);
}
}

@override
gpu.Shader createResource() => shader;
}

class Shader {
Expand Down
34 changes: 15 additions & 19 deletions packages/flame_3d/lib/src/resources/shader/uniform_array.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,23 @@ class UniformArray extends UniformInstance<UniformArrayKey, ByteBuffer> {
final List<Map<int, ({int hash, List<double> data})>> _storage = [];

@override
ByteBuffer? get resource {
if (super.resource == null) {
final data = <double>[];
for (final element in _storage) {
var previousIndex = -1;
for (final entry in element.entries) {
if (previousIndex + 1 != entry.key) {
final field = slot.fields.indexed
.firstWhere((e) => e.$1 == previousIndex + 1);
throw StateError(
'Uniform ${slot.name}.${field.$2} was not set',
);
}
previousIndex = entry.key;
data.addAll(entry.value.data);
ByteBuffer createResource() {
final data = <double>[];
for (final element in _storage) {
var previousIndex = -1;
for (final entry in element.entries) {
if (previousIndex + 1 != entry.key) {
final field =
slot.fields.indexed.firstWhere((e) => e.$1 == previousIndex + 1);
throw StateError(
'Uniform ${slot.name}.${field.$2} was not set',
);
}
previousIndex = entry.key;
data.addAll(entry.value.data);
}
super.resource = Float32List.fromList(data).buffer;
}

return super.resource;
return Float32List.fromList(data).buffer;
}

Map<int, ({int hash, List<double> data})> _get(int idx) {
Expand Down Expand Up @@ -67,7 +63,7 @@ class UniformArray extends UniformInstance<UniformArrayKey, ByteBuffer> {
storage[index] = (data: data, hash: hash);

// Clear the cache.
super.resource = null;
recreateResource = true;
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:flame_3d/resources.dart';
/// {@endtemplate}
abstract class UniformInstance<K, T> extends Resource<T?> {
/// {@macro uniform_instance}
UniformInstance(this.slot) : super(null);
UniformInstance(this.slot);

/// The slot this instance belongs too.
final UniformSlot slot;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:flame_3d/resources.dart';
/// Instance of a uniform sampler. Represented by a [Texture].
/// {@endtemplate}
class UniformSampler extends UniformInstance<void, Texture> {
Texture? texture;

/// {@macro uniform_sampler}
UniformSampler(super.slot);

Expand All @@ -15,9 +17,13 @@ class UniformSampler extends UniformInstance<void, Texture> {

@override
void set(void key, Texture value) {
resource = value;
texture = value;
recreateResource = true;
}

@override
Texture createResource() => texture!;

@override
void makeKey(int? idx, String? field) {}
}
13 changes: 11 additions & 2 deletions packages/flame_3d/lib/src/resources/shader/uniform_slot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import 'package:flutter_gpu/gpu.dart' as gpu;
/// {@endtemplate}
class UniformSlot extends Resource<gpu.UniformSlot?> {
UniformSlot._(this.name, this.fields, this._instanceCreator)
: _fieldIndices = {for (var (index, key) in fields.indexed) key: index},
super(null);
: _fieldIndices = {for (var (index, key) in fields.indexed) key: index};

/// {@macro uniform_slot}
///
Expand Down Expand Up @@ -51,4 +50,14 @@ class UniformSlot extends Resource<gpu.UniformSlot?> {
int indexOf(String field) => _fieldIndices[field]!;

UniformInstance create() => _instanceCreator.call(this);

gpu.UniformSlot? _uniformSlot;

set uniformSlot(gpu.UniformSlot value) {
_uniformSlot = value;
recreateResource = true;
}

@override
gpu.UniformSlot? createResource() => _uniformSlot;
}
37 changes: 16 additions & 21 deletions packages/flame_3d/lib/src/resources/shader/uniform_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,21 @@ class UniformValue extends UniformInstance<String, ByteBuffer> {
final Map<int, ({int hash, Float32List data})> _storage = HashMap();

@override
ByteBuffer? get resource {
if (super.resource == null) {
var previousIndex = -1;

final entries = _storage.entries.toList()
..sort(Comparing.on((c) => c.key));
final data = entries.fold<List<double>>([], (p, e) {
if (previousIndex + 1 != e.key) {
final field =
slot.fields.indexed.firstWhere((e) => e.$1 == previousIndex + 1);
throw StateError('Uniform ${slot.name}.${field.$2} was not set');
}
previousIndex = e.key;
return p..addAll(e.value.data);
});

super.resource = Float32List.fromList(data).buffer;
}

return super.resource;
ByteBuffer createResource() {
var previousIndex = -1;

final entries = _storage.entries.toList()..sort(Comparing.on((c) => c.key));
final data = entries.fold<List<double>>([], (p, e) {
if (previousIndex + 1 != e.key) {
final field =
slot.fields.indexed.firstWhere((e) => e.$1 == previousIndex + 1);
throw StateError('Uniform ${slot.name}.${field.$2} was not set');
}
previousIndex = e.key;
return p..addAll(e.value.data);
});

return Float32List.fromList(data).buffer;
}

Float32List? operator [](String key) => _storage[slot.indexOf(key)]?.data;
Expand All @@ -55,7 +50,7 @@ class UniformValue extends UniformInstance<String, ByteBuffer> {
_storage[index] = (data: data, hash: hash);

// Clear the cache.
super.resource = null;
recreateResource = true;
}

@override
Expand Down
Loading

0 comments on commit 4c5f726

Please sign in to comment.