Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Make resource creation be on demand to enable testing [PROPOSAL] #3411

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the new Resource design there is no longer a need to separate ShaderResource and Shader for testing purposes

one would be able to create a "Shader" in tests no problem as long as they don't call the .resource getter

if this new pattern is accepted, I can followup with updating this to be simpler as it was before

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
Loading