refactor: continual refactor to support multiple render targets

This commit is contained in:
Nick Fisher
2024-09-28 18:28:05 +08:00
parent 65e60da288
commit 921a994eb6
51 changed files with 1714 additions and 877 deletions

View File

@@ -0,0 +1,130 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
class ThermionFlutterAndroid
extends ThermionFlutterMethodChannelInterface {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterFFI");
ThermionViewerFFI? _viewer;
ThermionFlutterAndroid._() {}
RenderTarget? _renderTarget;
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterAndroid._();
}
final _textures = <ThermionFlutterTexture>{};
bool _creatingTexture = false;
bool _destroyingTexture = false;
bool _resizing = false;
///
/// Create a rendering surface.
///
/// This is internal; unless you are [thermion_*] package developer, don't
/// call this yourself.
///
/// The name here is slightly misleading because we only create
/// a texture render target on macOS and iOS; on Android, we render into
/// a native window derived from a Surface, and on Windows we render into
/// a HWND.
///
/// Currently, this only supports a single "texture" (aka rendering surface)
/// at any given time. If a [ThermionWidget] is disposed, it will call
/// [destroyTexture]; if it is resized, it will call [resizeTexture].
///
/// In future, we probably want to be able to create multiple distinct
/// textures/render targets. This would make it possible to have multiple
/// Flutter Texture widgets, each with its own Filament View attached.
/// The current design doesn't accommodate this (for example, it seems we can
/// only create a single native window from a Surface at any one time).
///
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
throw Exception("TODO");
// note that when [ThermionWidget] is disposed, we don't destroy the
// texture; instead, we keep it around in case a subsequent call requests
// a texture of the same size.
// if (_textures.length > 1) {
// throw Exception("Multiple textures not yet supported");
// } else if (_textures.length == 1) {
// if (_textures.first.height == physicalHeight &&
// _textures.first.width == physicalWidth) {
// return _textures.first;
// } else {
// await _viewer!.setRendering(false);
// await _swapChain?.destroy();
// await destroyTexture(_textures.first);
// _textures.clear();
// }
// }
// _creatingTexture = true;
// var result = await _channel.invokeMethod("createTexture",
// [physicalWidth, physicalHeight, offsetLeft, offsetLeft]);
// if (result == null || (result[0] == -1)) {
// throw Exception("Failed to create texture");
// }
// final flutterTextureId = result[0] as int?;
// final hardwareTextureId = result[1] as int?;
// final surfaceAddress = result[2] as int?;
// _logger.info(
// "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
// final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId,
// physicalWidth, physicalHeight, surfaceAddress);
// await _viewer?.createSwapChain(physicalWidth, physicalHeight,
// surface: texture.surfaceAddress == null
// ? nullptr
// : Pointer<Void>.fromAddress(texture.surfaceAddress!));
// if (texture.hardwareTextureId != null) {
// if (_renderTarget != null) {
// await _renderTarget!.destroy();
// }
// // ignore: unused_local_variable
// _renderTarget = await _viewer?.createRenderTarget(
// physicalWidth, physicalHeight, texture.hardwareTextureId!);
// }
// await _viewer?.updateViewportAndCameraProjection(
// physicalWidth.toDouble(), physicalHeight.toDouble());
// _creatingTexture = false;
// _textures.add(texture);
// return texture;
}
///
/// Called by [ThermionWidget] to resize a texture. Don't call this yourself.
///
@override
Future resizeWindow(
int width,
int height,
int offsetLeft,
int offsetTop,
) async {
throw Exception("Not supported on iOS");
}
}

View File

@@ -1,256 +1,4 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses a Flutter platform
/// channel to create a rendering context, resource loaders, and
/// render target(s).
///
class ThermionFlutterFFI extends ThermionFlutterPlatform {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterFFI");
ThermionViewerFFI? _viewer;
ThermionFlutterFFI._() {}
RenderTarget? _renderTarget;
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterFFI._();
}
final _textures = <ThermionFlutterTexture>{};
Future<ThermionViewer> createViewerWithOptions(
ThermionFlutterOptions options) async {
return createViewer(uberarchivePath: options.uberarchivePath);
}
Future<ThermionViewer> createViewer({String? uberarchivePath}) async {
var resourceLoader = Pointer<Void>.fromAddress(
await _channel.invokeMethod("getResourceLoaderWrapper"));
if (resourceLoader == nullptr) {
throw Exception("Failed to get resource loader");
}
var renderCallbackResult = await _channel.invokeMethod("getRenderCallback");
var renderCallback =
Pointer<NativeFunction<Void Function(Pointer<Void>)>>.fromAddress(
renderCallbackResult[0]);
var renderCallbackOwner =
Pointer<Void>.fromAddress(renderCallbackResult[1]);
var driverPlatform = await _channel.invokeMethod("getDriverPlatform");
var driverPtr = driverPlatform == null
? nullptr
: Pointer<Void>.fromAddress(driverPlatform);
var sharedContext = await _channel.invokeMethod("getSharedContext");
var sharedContextPtr = sharedContext == null
? nullptr
: Pointer<Void>.fromAddress(sharedContext);
_viewer = ThermionViewerFFI(
resourceLoader: resourceLoader,
renderCallback: renderCallback,
renderCallbackOwner: renderCallbackOwner,
driver: driverPtr,
sharedContext: sharedContextPtr,
uberArchivePath: uberarchivePath);
await _viewer!.initialized;
return _viewer!;
}
bool _creatingTexture = false;
bool _destroyingTexture = false;
Future _waitForTextureCreationToComplete() async {
var iter = 0;
while (_creatingTexture || _destroyingTexture) {
await Future.delayed(Duration(milliseconds: 50));
iter++;
if (iter > 10) {
throw Exception(
"Previous call to createTexture failed to complete within 500ms");
}
}
}
///
/// Create a backing surface for rendering.
/// This is called by [ThermionWidget]; don't call this yourself.
///
/// The name here is slightly misleading because we only create
/// a texture render target on macOS and iOS; on Android, we render into
/// a native window derived from a Surface, and on Windows we render into
/// a HWND.
///
/// Currently, this only supports a single "texture" (aka rendering surface)
/// at any given time. If a [ThermionWidget] is disposed, it will call
/// [destroyTexture]; if it is resized, it will call [resizeTexture].
///
/// In future, we probably want to be able to create multiple distinct
/// textures/render targets. This would make it possible to have multiple
/// Flutter Texture widgets, each with its own Filament View attached.
/// The current design doesn't accommodate this (for example, it seems we can
/// only create a single native window from a Surface at any one time).
///
Future<ThermionFlutterTexture?> createTexture(double width, double height,
double offsetLeft, double offsetRight, double pixelRatio) async {
final physicalWidth = (width * pixelRatio).ceil();
final physicalHeight = (height * pixelRatio).ceil();
// when a ThermionWidget is inserted, disposed then immediately reinserted
// into the widget hierarchy (e.g. rebuilding due to setState(() {}) being called in an ancestor widget)
// the first call to createTexture may not have completed before the second.
// add a loop here to wait (max 500ms) for the first call to complete
await _waitForTextureCreationToComplete();
// note that when [ThermionWidget] is disposed, we don't destroy the
// texture; instead, we keep it around in case a subsequent call requests
// a texture of the same size.
if (_textures.length > 1) {
throw Exception("Multiple textures not yet supported");
} else if (_textures.length == 1) {
if (_textures.first.height == physicalHeight &&
_textures.first.width == physicalWidth) {
return _textures.first;
} else {
await _viewer!.setRendering(false);
await _swapChain?.destroy();
await destroyTexture(_textures.first);
_textures.clear();
}
}
_creatingTexture = true;
var result = await _channel.invokeMethod("createTexture",
[physicalWidth, physicalHeight, offsetLeft, offsetLeft]);
if (result == null || (result[0] == -1)) {
throw Exception("Failed to create texture");
}
final flutterTextureId = result[0] as int?;
final hardwareTextureId = result[1] as int?;
final surfaceAddress = result[2] as int?;
_logger.info(
"Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
_viewer?.viewportDimensions =
(physicalWidth.toDouble(), physicalHeight.toDouble());
final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId,
physicalWidth, physicalHeight, surfaceAddress);
await _viewer?.createSwapChain(physicalWidth, physicalHeight,
surface: texture.surfaceAddress == null
? nullptr
: Pointer<Void>.fromAddress(texture.surfaceAddress!));
if (texture.hardwareTextureId != null) {
if (_renderTarget != null) {
await _renderTarget!.destroy();
}
// ignore: unused_local_variable
_renderTarget = await _viewer?.createRenderTarget(
physicalWidth,
physicalHeight,
texture.hardwareTextureId!);
}
await _viewer?.updateViewportAndCameraProjection(
physicalWidth.toDouble(), physicalHeight.toDouble());
_creatingTexture = false;
_textures.add(texture);
return texture;
}
///
/// Destroy a texture and clean up the texture cache (if applicable).
///
Future destroyTexture(ThermionFlutterTexture texture) async {
if (_creatingTexture || _destroyingTexture) {
throw Exception(
"Cannot destroy texture while concurrent call to createTexture/destroyTexture has not completed");
}
_destroyingTexture = true;
_textures.remove(texture);
await _channel.invokeMethod("destroyTexture", texture.flutterTextureId);
_destroyingTexture = false;
}
bool _resizing = false;
///
/// Called by [ThermionWidget] to resize a texture. Don't call this yourself.
///
@override
Future<ThermionFlutterTexture?> resizeTexture(
ThermionFlutterTexture texture,
int width,
int height,
int offsetLeft,
int offsetTop,
double pixelRatio) async {
if (_resizing) {
throw Exception("Resize underway");
}
width = (width * pixelRatio).ceil();
height = (height * pixelRatio).ceil();
if ((width - _viewer!.viewportDimensions.$1).abs() < 0.001 ||
(height - _viewer!.viewportDimensions.$2).abs() < 0.001) {
return texture;
}
_resizing = true;
bool wasRendering = _viewer!.rendering;
await _viewer!.setRendering(false);
await _swapChain?.destroy();
await destroyTexture(texture);
var result = await _channel
.invokeMethod("createTexture", [width, height, offsetLeft, offsetLeft]);
if (result == null || result[0] == -1) {
throw Exception("Failed to create texture");
}
_viewer!.viewportDimensions = (width.toDouble(), height.toDouble());
var newTexture =
ThermionFlutterTexture(result[0], result[1], width, height, result[2]);
await _viewer!.createSwapChain(width, height,
surface: newTexture.surfaceAddress == null
? nullptr
: Pointer<Void>.fromAddress(newTexture.surfaceAddress!));
if (newTexture.hardwareTextureId != null) {
// ignore: unused_local_variable
var renderTarget = await _viewer!.createRenderTarget(
width, height, newTexture.hardwareTextureId!);
}
await _viewer!
.updateViewportAndCameraProjection(width.toDouble(), height.toDouble());
_viewer!.viewportDimensions = (width.toDouble(), height.toDouble());
if (wasRendering) {
await _viewer!.setRendering(true);
}
_textures.add(newTexture);
_resizing = false;
return newTexture;
}
}
export 'thermion_flutter_android.dart';
export 'thermion_flutter_macos.dart';
export 'thermion_flutter_windows.dart';
export 'thermion_flutter_ios.dart';

View File

@@ -0,0 +1,131 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
class ThermionFlutterIOS
extends ThermionFlutterMethodChannelInterface {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterFFI");
ThermionViewerFFI? _viewer;
ThermionFlutterIOS._() {}
RenderTarget? _renderTarget;
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterIOS._();
}
final _textures = <ThermionFlutterTexture>{};
bool _creatingTexture = false;
bool _destroyingTexture = false;
bool _resizing = false;
///
/// Create a rendering surface.
///
/// This is internal; unless you are [thermion_*] package developer, don't
/// call this yourself.
///
/// The name here is slightly misleading because we only create
/// a texture render target on macOS and iOS; on Android, we render into
/// a native window derived from a Surface, and on Windows we render into
/// a HWND.
///
/// Currently, this only supports a single "texture" (aka rendering surface)
/// at any given time. If a [ThermionWidget] is disposed, it will call
/// [destroyTexture]; if it is resized, it will call [resizeTexture].
///
/// In future, we probably want to be able to create multiple distinct
/// textures/render targets. This would make it possible to have multiple
/// Flutter Texture widgets, each with its own Filament View attached.
/// The current design doesn't accommodate this (for example, it seems we can
/// only create a single native window from a Surface at any one time).
///
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
throw Exception("TODO");
// note that when [ThermionWidget] is disposed, we don't destroy the
// texture; instead, we keep it around in case a subsequent call requests
// a texture of the same size.
// if (_textures.length > 1) {
// throw Exception("Multiple textures not yet supported");
// } else if (_textures.length == 1) {
// if (_textures.first.height == physicalHeight &&
// _textures.first.width == physicalWidth) {
// return _textures.first;
// } else {
// await _viewer!.setRendering(false);
// await _swapChain?.destroy();
// await destroyTexture(_textures.first);
// _textures.clear();
// }
// }
// _creatingTexture = true;
// var result = await _channel.invokeMethod("createTexture",
// [physicalWidth, physicalHeight, offsetLeft, offsetLeft]);
// if (result == null || (result[0] == -1)) {
// throw Exception("Failed to create texture");
// }
// final flutterTextureId = result[0] as int?;
// final hardwareTextureId = result[1] as int?;
// final surfaceAddress = result[2] as int?;
// _logger.info(
// "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
// final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId,
// physicalWidth, physicalHeight, surfaceAddress);
// await _viewer?.createSwapChain(physicalWidth, physicalHeight,
// surface: texture.surfaceAddress == null
// ? nullptr
// : Pointer<Void>.fromAddress(texture.surfaceAddress!));
// if (texture.hardwareTextureId != null) {
// if (_renderTarget != null) {
// await _renderTarget!.destroy();
// }
// // ignore: unused_local_variable
// _renderTarget = await _viewer?.createRenderTarget(
// physicalWidth, physicalHeight, texture.hardwareTextureId!);
// }
// await _viewer?.updateViewportAndCameraProjection(
// physicalWidth.toDouble(), physicalHeight.toDouble());
// _creatingTexture = false;
// _textures.add(texture);
// return texture;
}
///
/// Called by [ThermionWidget] to resize a texture. Don't call this yourself.
///
@override
Future resizeWindow(
int width,
int height,
int offsetLeft,
int offsetTop,
) async {
throw Exception("Not supported on iOS");
}
}

View File

@@ -0,0 +1,77 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterFFI");
SwapChain? _swapChain;
ThermionFlutterMacOS._() {}
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterMacOS._();
}
// On desktop platforms, textures are always created
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
if (_swapChain == null) {
// this is the headless swap chain
// since we will be using render targets, the actual dimensions don't matter
_swapChain = await viewer!.createSwapChain(width, height);
}
// Get screen width and height
int screenWidth = width; //1920;
int screenHeight = height; //1080;
if (width > screenWidth || height > screenHeight) {
throw Exception("TODO - unsupported");
}
var result = await _channel
.invokeMethod("createTexture", [screenWidth, screenHeight, 0, 0]);
if (result == null || (result[0] == -1)) {
throw Exception("Failed to create texture");
}
final flutterTextureId = result[0] as int?;
final hardwareTextureId = result[1] as int?;
final surfaceAddress = result[2] as int?;
_logger.info(
"Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
return MacOSMethodChannelFlutterTexture(_channel, flutterTextureId!,
hardwareTextureId!, screenWidth, screenHeight);
}
// On MacOS, we currently use textures/render targets, so there's no window to resize
@override
Future<ThermionFlutterTexture?> resizeWindow(
int width, int height, int offsetTop, int offsetRight) {
throw UnimplementedError();
}
}
class MacOSMethodChannelFlutterTexture extends MethodChannelFlutterTexture {
MacOSMethodChannelFlutterTexture(super.channel, super.flutterId,
super.hardwareId, super.width, super.height);
@override
Future resize(int width, int height, int left, int top) async {
if (width > this.width || height > this.height || left != 0 || top != 0) {
throw Exception();
}
}
}

View File

@@ -0,0 +1,86 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:logging/logging.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
///
/// An abstract implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
abstract class ThermionFlutterMethodChannelInterface
extends ThermionFlutterPlatform {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterMethodChannelInterface");
ThermionViewerFFI? viewer;
Future<ThermionViewer> createViewer({ThermionFlutterOptions? options}) async {
if (viewer != null) {
throw Exception(
"Only one viewer can be created over the lifetime of an application");
}
var resourceLoader = Pointer<Void>.fromAddress(
await _channel.invokeMethod("getResourceLoaderWrapper"));
if (resourceLoader == nullptr) {
throw Exception("Failed to get resource loader");
}
var renderCallback = nullptr;
var renderCallbackOwner = nullptr;
var driverPlatform = await _channel.invokeMethod("getDriverPlatform");
var driverPtr = driverPlatform == null
? nullptr
: Pointer<Void>.fromAddress(driverPlatform);
var sharedContext = await _channel.invokeMethod("getSharedContext");
var sharedContextPtr = sharedContext == null
? nullptr
: Pointer<Void>.fromAddress(sharedContext);
viewer = ThermionViewerFFI(
resourceLoader: resourceLoader,
renderCallback: renderCallback,
renderCallbackOwner: renderCallbackOwner,
driver: driverPtr,
sharedContext: sharedContextPtr,
uberArchivePath: options?.uberarchivePath);
await viewer!.initialized;
return viewer!;
}
}
abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture {
final MethodChannel _channel;
MethodChannelFlutterTexture(
this._channel, this.flutterId, this.hardwareId, this.width, this.height);
Future destroy() async {
await _channel.invokeMethod("destroyTexture", hardwareId);
}
@override
final int flutterId;
@override
final int hardwareId;
@override
final int height;
@override
final int width;
Future markFrameAvailable() async {
await _channel.invokeMethod("markTextureFrameAvailable", this.flutterId);
}
}

View File

@@ -0,0 +1,97 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ffi';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
class ThermionFlutterWindows
extends ThermionFlutterMethodChannelInterface {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterWindows");
ThermionViewerFFI? _viewer;
ThermionFlutterWindows._() {}
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterWindows._();
}
///
/// Not supported on Windows. Throws an exception.
///
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
throw Exception("Texture not supported on Windows");
}
bool _resizing = false;
///
/// Called by [ThermionWidget] to resize a texture. Don't call this yourself.
///
@override
Future resizeWindow(
int width, int height, int offsetLeft, int offsetTop) async {
if (_resizing) {
throw Exception("Resize underway");
}
throw Exception("TODO");
// final view = await this._viewer!.getViewAt(0);
// final viewport = await view.getViewport();
// final swapChain = await this._viewer.getSwapChainAt(0);
// if (width == viewport.width && height - viewport.height == 0) {
// return;
// }
// _resizing = true;
// bool wasRendering = _viewer!.rendering;
// await _viewer!.setRendering(false);
// await _swapChain?.destroy();
// var result = await _channel
// .invokeMethod("createTexture", [width, height, offsetLeft, offsetLeft]);
// if (result == null || result[0] == -1) {
// throw Exception("Failed to create texture");
// }
// var newTexture =
// ThermionFlutterTexture(result[0], result[1], width, height, result[2]);
// await _viewer!.createSwapChain(width, height,
// surface: newTexture.surfaceAddress == null
// ? nullptr
// : Pointer<Void>.fromAddress(newTexture.surfaceAddress!));
// if (newTexture.hardwareTextureId != null) {
// // ignore: unused_local_variable
// var renderTarget = await _viewer!
// .createRenderTarget(width, height, newTexture.hardwareTextureId!);
// }
// await _viewer!
// .updateViewportAndCameraProjection(width.toDouble(), height.toDouble());
// if (wasRendering) {
// await _viewer!.setRendering(true);
// }
// _textures.add(newTexture);
// _resizing = false;
// return newTexture;
}
}