From 96b5403d1447aba0927528b2b6f4df1d212a8e7c Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Fri, 19 Dec 2025 22:46:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(render):=20=E6=8A=BD=E8=B1=A1=E5=9B=BE?= =?UTF-8?q?=E5=BD=A2=E5=90=8E=E7=AB=AF=E5=B9=B6=E8=BF=81=E7=A7=BB=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=20(#313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(render): 抽象图形后端并迁移渲染器 - 新增 engine-shared 包,定义 GraphicsBackend trait 抽象层 - 实现 WebGL2Backend 作为首个后端实现 - 迁移 Renderer2D、SpriteBatch、GridRenderer、GizmoRenderer 使用新抽象 - 修复 VAO 创建时索引缓冲区绑定状态泄漏问题 - 新增 create_vertex_buffer_sized 方法支持预分配缓冲区 * fix(serialization): 修复序列化循环引用导致栈溢出 - 在 serializeValue 添加 WeakSet 检测循环引用 - 跳过已访问对象避免无限递归 * refactor(serialization): 提取 ValueSerializer 统一序列化逻辑 - 新增 ValueSerializer 模块,函数式设计 - 支持可扩展类型处理器注册 - 移除 ComponentSerializer/SceneSerializer 重复代码 - 内置 Date/Map/Set 类型支持 * fix: CodeQL 类型检查警告 --- .gitignore | 3 + .../ECS/Serialization/ComponentSerializer.ts | 337 +----- .../src/ECS/Serialization/SceneSerializer.ts | 121 +-- .../src/ECS/Serialization/ValueSerializer.ts | 113 ++ packages/core/src/ECS/Serialization/index.ts | 4 + packages/engine-shared/Cargo.lock | 127 +++ packages/engine-shared/Cargo.toml | 33 + packages/engine-shared/src/batch/mod.rs | 7 + .../engine-shared/src/batch/sprite_data.rs | 417 ++++++++ packages/engine-shared/src/camera.rs | 421 ++++++++ packages/engine-shared/src/lib.rs | 40 + packages/engine-shared/src/traits/backend.rs | 519 +++++++++ packages/engine-shared/src/traits/mod.rs | 7 + packages/engine-shared/src/traits/platform.rs | 441 ++++++++ packages/engine-shared/src/traits/renderer.rs | 293 ++++++ packages/engine-shared/src/types/blend.rs | 333 ++++++ packages/engine-shared/src/types/handle.rs | 400 +++++++ packages/engine-shared/src/types/mod.rs | 9 + packages/engine-shared/src/types/texture.rs | 383 +++++++ packages/engine-shared/src/types/uniform.rs | 307 ++++++ packages/engine-shared/src/types/vertex.rs | 352 +++++++ packages/engine/Cargo.toml | 3 + packages/engine/src/backend/mod.rs | 10 + packages/engine/src/backend/webgl2.rs | 962 +++++++++++++++++ packages/engine/src/core/engine.rs | 184 ++-- packages/engine/src/lib.rs | 37 + .../engine/src/renderer/batch/sprite_batch.rs | 507 ++------- packages/engine/src/renderer/gizmo.rs | 988 +++++------------- packages/engine/src/renderer/grid.rs | 345 +++--- packages/engine/src/renderer/renderer2d.rs | 490 ++++----- .../src/renderer/texture/texture_manager.rs | 17 +- 31 files changed, 6096 insertions(+), 2114 deletions(-) create mode 100644 packages/core/src/ECS/Serialization/ValueSerializer.ts create mode 100644 packages/engine-shared/Cargo.lock create mode 100644 packages/engine-shared/Cargo.toml create mode 100644 packages/engine-shared/src/batch/mod.rs create mode 100644 packages/engine-shared/src/batch/sprite_data.rs create mode 100644 packages/engine-shared/src/camera.rs create mode 100644 packages/engine-shared/src/lib.rs create mode 100644 packages/engine-shared/src/traits/backend.rs create mode 100644 packages/engine-shared/src/traits/mod.rs create mode 100644 packages/engine-shared/src/traits/platform.rs create mode 100644 packages/engine-shared/src/traits/renderer.rs create mode 100644 packages/engine-shared/src/types/blend.rs create mode 100644 packages/engine-shared/src/types/handle.rs create mode 100644 packages/engine-shared/src/types/mod.rs create mode 100644 packages/engine-shared/src/types/texture.rs create mode 100644 packages/engine-shared/src/types/uniform.rs create mode 100644 packages/engine-shared/src/types/vertex.rs create mode 100644 packages/engine/src/backend/mod.rs create mode 100644 packages/engine/src/backend/webgl2.rs diff --git a/.gitignore b/.gitignore index 99df7b23..cd52e9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ docs/.vitepress/dist/ # Tauri 捆绑输出 **/src-tauri/target/release/bundle/ **/src-tauri/target/debug/bundle/ + +# Rust 构建产物 +**/engine-shared/target/ diff --git a/packages/core/src/ECS/Serialization/ComponentSerializer.ts b/packages/core/src/ECS/Serialization/ComponentSerializer.ts index 9bb0954b..de240574 100644 --- a/packages/core/src/ECS/Serialization/ComponentSerializer.ts +++ b/packages/core/src/ECS/Serialization/ComponentSerializer.ts @@ -1,363 +1,130 @@ /** * 组件序列化器 * - * 负责组件的序列化和反序列化操作 + * Component serializer for ECS components. */ import { Component } from '../Component'; import { ComponentType } from '../Core/ComponentStorage'; import { getComponentTypeName, isEntityRefProperty } from '../Decorators'; -import { - getSerializationMetadata -} from './SerializationDecorators'; +import { getSerializationMetadata } from './SerializationDecorators'; +import { ValueSerializer, SerializableValue } from './ValueSerializer'; import type { Entity } from '../Entity'; import type { SerializationContext, SerializedEntityRef } from './SerializationContext'; -/** - * 可序列化的值类型 - */ -export type SerializableValue = - | string - | number - | boolean - | null - | undefined - | SerializableValue[] - | { [key: string]: SerializableValue } - | { __type: 'Date'; value: string } - | { __type: 'Map'; value: Array<[SerializableValue, SerializableValue]> } - | { __type: 'Set'; value: SerializableValue[] } - | { __entityRef: SerializedEntityRef }; +export type { SerializableValue } from './ValueSerializer'; -/** - * 序列化后的组件数据 - */ export interface SerializedComponent { - /** - * 组件类型名称 - */ type: string; - - /** - * 序列化版本 - */ version: number; - - /** - * 组件数据 - */ data: Record; } -/** - * 组件序列化器类 - */ export class ComponentSerializer { - /** - * 序列化单个组件 - * - * @param component 要序列化的组件实例 - * @returns 序列化后的组件数据,如果组件不可序列化则返回null - */ - public static serialize(component: Component): SerializedComponent | null { + static serialize(component: Component): SerializedComponent | null { const metadata = getSerializationMetadata(component); - - if (!metadata) { - // 组件没有使用@Serializable装饰器,不可序列化 - return null; - } + if (!metadata) return null; const componentType = component.constructor as ComponentType; const typeName = metadata.options.typeId || getComponentTypeName(componentType); const data: Record = {}; - // 序列化标记的字段 for (const [fieldName, options] of metadata.fields) { + if (metadata.ignoredFields.has(fieldName)) continue; + const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName; const value = (component as unknown as Record)[fieldName]; - // 跳过忽略的字段 - if (metadata.ignoredFields.has(fieldName)) { - continue; - } - let serializedValue: SerializableValue; - - // 检查是否为 EntityRef 属性 if (isEntityRefProperty(component, fieldKey)) { serializedValue = this.serializeEntityRef(value as Entity | null); } else if (options.serializer) { - // 使用自定义序列化器 serializedValue = options.serializer(value); } else { - // 使用默认序列化 - serializedValue = this.serializeValue(value as SerializableValue); + serializedValue = ValueSerializer.serialize(value); } - // 使用别名或原始字段名 - const key = options.alias || fieldKey; - data[key] = serializedValue; + data[options.alias || fieldKey] = serializedValue; } - return { - type: typeName, - version: metadata.options.version, - data - }; + return { type: typeName, version: metadata.options.version, data }; } - /** - * 反序列化组件 - * - * @param serializedData 序列化的组件数据 - * @param componentRegistry 组件类型注册表 (类型名 -> 构造函数) - * @param context 序列化上下文(可选,用于解析 EntityRef) - * @returns 反序列化后的组件实例,如果失败则返回null - */ - public static deserialize( + static deserialize( serializedData: SerializedComponent, componentRegistry: Map, context?: SerializationContext ): Component | null { const componentClass = componentRegistry.get(serializedData.type); - if (!componentClass) { - console.warn(`未找到组件类型: ${serializedData.type}`); + console.warn(`Component type not found: ${serializedData.type}`); return null; } const metadata = getSerializationMetadata(componentClass); - if (!metadata) { - console.warn(`组件 ${serializedData.type} 不可序列化`); + console.warn(`Component ${serializedData.type} is not serializable`); return null; } - // 创建组件实例 const component = new componentClass(); - // 反序列化字段 for (const [fieldName, options] of metadata.fields) { const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName; const key = options.alias || fieldKey; const serializedValue = serializedData.data[key]; - if (serializedValue === undefined) { - continue; // 字段不存在于序列化数据中 - } + if (serializedValue === undefined) continue; - // 检查是否为序列化的 EntityRef if (this.isSerializedEntityRef(serializedValue)) { - // EntityRef 需要延迟解析 if (context) { const ref = serializedValue.__entityRef; context.registerPendingRef(component, fieldKey, ref.id, ref.guid); } - // 暂时设为 null,后续由 context.resolveAllReferences() 填充 (component as unknown as Record)[fieldName] = null; continue; } - // 使用自定义反序列化器或默认反序列化 const value = options.deserializer ? options.deserializer(serializedValue) - : this.deserializeValue(serializedValue); + : ValueSerializer.deserialize(serializedValue); - (component as unknown as Record)[fieldName] = value; + (component as unknown as Record)[fieldName] = value; } return component; } - /** - * 批量序列化组件 - * - * @param components 组件数组 - * @returns 序列化后的组件数据数组 - */ - public static serializeComponents(components: Component[]): SerializedComponent[] { - const result: SerializedComponent[] = []; - - for (const component of components) { - const serialized = this.serialize(component); - if (serialized) { - result.push(serialized); - } - } - - return result; + static serializeComponents(components: Component[]): SerializedComponent[] { + return components + .map(c => this.serialize(c)) + .filter((s): s is SerializedComponent => s !== null); } - /** - * 批量反序列化组件 - * - * @param serializedComponents 序列化的组件数据数组 - * @param componentRegistry 组件类型注册表 - * @param context 序列化上下文(可选,用于解析 EntityRef) - * @returns 反序列化后的组件数组 - */ - public static deserializeComponents( + static deserializeComponents( serializedComponents: SerializedComponent[], componentRegistry: Map, context?: SerializationContext ): Component[] { - const result: Component[] = []; - - for (const serialized of serializedComponents) { - const component = this.deserialize(serialized, componentRegistry, context); - if (component) { - result.push(component); - } - } - - return result; + return serializedComponents + .map(s => this.deserialize(s, componentRegistry, context)) + .filter((c): c is Component => c !== null); } - /** - * 默认值序列化 - * - * 处理基本类型、数组、对象等的序列化 - */ - private static serializeValue(value: SerializableValue): SerializableValue { - if (value === null || value === undefined) { - return value; - } - - // 基本类型 - const type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean') { - return value; - } - - // 日期 - if (value instanceof Date) { - return { - __type: 'Date', - value: value.toISOString() - }; - } - - // 数组 - if (Array.isArray(value)) { - return value.map((item) => this.serializeValue(item)); - } - - // Map (如果没有使用@SerializeMap装饰器) - if (value instanceof Map) { - return { - __type: 'Map', - value: Array.from(value.entries()) - }; - } - - // Set - if (value instanceof Set) { - return { - __type: 'Set', - value: Array.from(value) - }; - } - - // 普通对象 - if (type === 'object' && typeof value === 'object' && !Array.isArray(value)) { - const result: Record = {}; - const obj = value as Record; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - result[key] = this.serializeValue(obj[key]); - } - } - return result; - } - - // 其他类型(函数等)不序列化 - return undefined; - } - - /** - * 默认值反序列化 - */ - private static deserializeValue(value: SerializableValue): SerializableValue { - if (value === null || value === undefined) { - return value; - } - - // 基本类型直接返回 - const type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean') { - return value; - } - - // 处理特殊类型标记 - if (type === 'object' && typeof value === 'object' && '__type' in value) { - const typedValue = value as { __type: string; value: SerializableValue }; - switch (typedValue.__type) { - case 'Date': - return { __type: 'Date', value: typeof typedValue.value === 'string' ? typedValue.value : String(typedValue.value) }; - case 'Map': - return { __type: 'Map', value: typedValue.value as Array<[SerializableValue, SerializableValue]> }; - case 'Set': - return { __type: 'Set', value: typedValue.value as SerializableValue[] }; - } - } - - // 数组 - if (Array.isArray(value)) { - return value.map((item) => this.deserializeValue(item)); - } - - // 普通对象 - if (type === 'object' && typeof value === 'object' && !Array.isArray(value)) { - const result: Record = {}; - const obj = value as Record; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - result[key] = this.deserializeValue(obj[key]); - } - } - return result; - } - - return value; - } - - /** - * 验证序列化数据的版本 - * - * @param serializedData 序列化数据 - * @param expectedVersion 期望的版本号 - * @returns 版本是否匹配 - */ - public static validateVersion( - serializedData: SerializedComponent, - expectedVersion: number - ): boolean { + static validateVersion(serializedData: SerializedComponent, expectedVersion: number): boolean { return serializedData.version === expectedVersion; } - /** - * 获取组件的序列化信息 - * - * @param component 组件实例或组件类 - * @returns 序列化信息对象,包含类型名、版本、可序列化字段列表 - */ - public static getSerializationInfo(component: Component | ComponentType): { + static getSerializationInfo(component: Component | ComponentType): { type: string; version: number; fields: string[]; ignoredFields: string[]; isSerializable: boolean; - } | null { + } { const metadata = getSerializationMetadata(component); - if (!metadata) { - return { - type: 'unknown', - version: 0, - fields: [], - ignoredFields: [], - isSerializable: false - }; + return { type: 'unknown', version: 0, fields: [], ignoredFields: [], isSerializable: false }; } const componentType = typeof component === 'function' @@ -367,50 +134,18 @@ export class ComponentSerializer { return { type: metadata.options.typeId || getComponentTypeName(componentType), version: metadata.options.version, - fields: Array.from(metadata.fields.keys()).map((k) => - typeof k === 'symbol' ? k.toString() : k - ), - ignoredFields: Array.from(metadata.ignoredFields).map((k) => - typeof k === 'symbol' ? k.toString() : k - ), + fields: Array.from(metadata.fields.keys()).map(k => typeof k === 'symbol' ? k.toString() : k), + ignoredFields: Array.from(metadata.ignoredFields).map(k => typeof k === 'symbol' ? k.toString() : k), isSerializable: true }; } - /** - * 序列化 Entity 引用 - * - * Serialize an Entity reference to a portable format. - * - * @param entity Entity 实例或 null - * @returns 序列化的引用格式 - */ - public static serializeEntityRef(entity: Entity | null): SerializableValue { - if (!entity) { - return null; - } - - return { - __entityRef: { - id: entity.id, - guid: entity.persistentId - } - }; + static serializeEntityRef(entity: Entity | null): SerializableValue { + if (!entity) return null; + return { __entityRef: { id: entity.id, guid: entity.persistentId } }; } - /** - * 检查值是否为序列化的 EntityRef - * - * Check if a value is a serialized EntityRef. - * - * @param value 要检查的值 - * @returns 如果是 EntityRef 返回 true - */ - public static isSerializedEntityRef(value: unknown): value is { __entityRef: SerializedEntityRef } { - return ( - typeof value === 'object' && - value !== null && - '__entityRef' in value - ); + static isSerializedEntityRef(value: unknown): value is { __entityRef: SerializedEntityRef } { + return typeof value === 'object' && value !== null && '__entityRef' in value; } } diff --git a/packages/core/src/ECS/Serialization/SceneSerializer.ts b/packages/core/src/ECS/Serialization/SceneSerializer.ts index b4ee866c..6e60b642 100644 --- a/packages/core/src/ECS/Serialization/SceneSerializer.ts +++ b/packages/core/src/ECS/Serialization/SceneSerializer.ts @@ -14,6 +14,7 @@ import { BinarySerializer } from '../../Utils/BinarySerializer'; import { HierarchySystem } from '../Systems/HierarchySystem'; import { HierarchyComponent } from '../Components/HierarchyComponent'; import { SerializationContext } from './SerializationContext'; +import { ValueSerializer, SerializableValue } from './ValueSerializer'; /** * 场景序列化格式 @@ -387,131 +388,21 @@ export class SceneSerializer { } } - /** - * 序列化场景自定义数据 - * - * 将 Map 转换为普通对象 - */ - private static serializeSceneData(sceneData: Map): Record { - const result: Record = {}; - + private static serializeSceneData(sceneData: Map): Record { + const result: Record = {}; for (const [key, value] of sceneData) { - result[key] = this.serializeValue(value); + result[key] = ValueSerializer.serialize(value); } - return result; } - /** - * 反序列化场景自定义数据 - * - * 将普通对象还原为 Map - */ - private static deserializeSceneData( - data: Record, - targetMap: Map - ): void { + private static deserializeSceneData(data: Record, targetMap: Map): void { targetMap.clear(); - for (const [key, value] of Object.entries(data)) { - targetMap.set(key, this.deserializeValue(value)); + targetMap.set(key, ValueSerializer.deserialize(value as SerializableValue)); } } - /** - * 序列化单个值 - */ - private static serializeValue(value: any): any { - if (value === null || value === undefined) { - return value; - } - - // 基本类型 - const type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean') { - return value; - } - - // Date - if (value instanceof Date) { - return { __type: 'Date', value: value.toISOString() }; - } - - // Map - if (value instanceof Map) { - return { __type: 'Map', value: Array.from(value.entries()) }; - } - - // Set - if (value instanceof Set) { - return { __type: 'Set', value: Array.from(value) }; - } - - // 数组 - if (Array.isArray(value)) { - return value.map((item) => this.serializeValue(item)); - } - - // 普通对象 - if (type === 'object') { - const result: Record = {}; - for (const key in value) { - if (value.hasOwnProperty(key)) { - result[key] = this.serializeValue(value[key]); - } - } - return result; - } - - // 其他类型不序列化 - return undefined; - } - - /** - * 反序列化单个值 - */ - private static deserializeValue(value: any): any { - if (value === null || value === undefined) { - return value; - } - - // 基本类型 - const type = typeof value; - if (type === 'string' || type === 'number' || type === 'boolean') { - return value; - } - - // 处理特殊类型标记 - if (type === 'object' && value.__type) { - switch (value.__type) { - case 'Date': - return new Date(value.value); - case 'Map': - return new Map(value.value); - case 'Set': - return new Set(value.value); - } - } - - // 数组 - if (Array.isArray(value)) { - return value.map((item) => this.deserializeValue(item)); - } - - // 普通对象 - if (type === 'object') { - const result: Record = {}; - for (const key in value) { - if (value.hasOwnProperty(key)) { - result[key] = this.deserializeValue(value[key]); - } - } - return result; - } - - return value; - } - /** * 过滤要序列化的实体和组件 */ diff --git a/packages/core/src/ECS/Serialization/ValueSerializer.ts b/packages/core/src/ECS/Serialization/ValueSerializer.ts new file mode 100644 index 00000000..f0b837ff --- /dev/null +++ b/packages/core/src/ECS/Serialization/ValueSerializer.ts @@ -0,0 +1,113 @@ +/** + * 值序列化器 + * + * Value serializer with circular reference detection and extensible type handlers. + */ + +export type PrimitiveValue = string | number | boolean | null | undefined; + +export type SerializableValue = + | PrimitiveValue + | SerializableValue[] + | { readonly [key: string]: SerializableValue } + | { readonly __type: string; readonly value: unknown }; + +type Serializer = (value: T, serialize: (v: unknown) => SerializableValue) => SerializableValue; +type Deserializer = (data: { __type: string; value: unknown }) => T; + +interface TypeDef { + check: (value: unknown) => value is T; + serialize: Serializer; + deserialize: Deserializer; +} + +const types = new Map(); + +function registerType(name: string, def: TypeDef): void { + types.set(name, def as TypeDef); +} + +// 内置类型 +registerType('Date', { + check: (v): v is Date => v instanceof Date, + serialize: (v) => ({ __type: 'Date', value: v.toISOString() }), + deserialize: (d) => new Date(d.value as string) +}); + +registerType>('Map', { + check: (v): v is Map => v instanceof Map, + serialize: (v, ser) => ({ __type: 'Map', value: [...v].map(([k, val]) => [ser(k), ser(val)]) }), + deserialize: (d) => new Map(d.value as Array<[unknown, unknown]>) +}); + +registerType>('Set', { + check: (v): v is Set => v instanceof Set, + serialize: (v, ser) => ({ __type: 'Set', value: [...v].map(ser) }), + deserialize: (d) => new Set(d.value as unknown[]) +}); + +function serialize(value: unknown, seen = new WeakSet()): SerializableValue { + if (value == null) return value as null | undefined; + + const t = typeof value; + if (t === 'string' || t === 'number' || t === 'boolean') return value as PrimitiveValue; + if (t === 'function') return undefined; + + const obj = value as object; + if (seen.has(obj)) return undefined; + seen.add(obj); + + for (const [, def] of types) { + if (def.check(value)) { + return def.serialize(value, (v) => serialize(v, seen)); + } + } + + if (Array.isArray(value)) { + return value.map((v) => serialize(v, seen)); + } + + const result: Record = {}; + for (const k of Object.keys(value as object)) { + result[k] = serialize((value as Record)[k], seen); + } + return result; +} + +function deserialize(value: SerializableValue): unknown { + if (value == null) return value; + + const t = typeof value; + if (t === 'string' || t === 'number' || t === 'boolean') return value; + + if (isTypedValue(value)) { + const def = types.get(value.__type); + return def ? def.deserialize(value) : value; + } + + if (Array.isArray(value)) { + return value.map(deserialize); + } + + const result: Record = {}; + for (const k of Object.keys(value)) { + result[k] = deserialize((value as Record)[k]); + } + return result; +} + +function isTypedValue(v: unknown): v is { __type: string; value: unknown } { + if (v === null || typeof v !== 'object') { + return false; + } + return '__type' in v; +} + +export const ValueSerializer = { + serialize, + deserialize, + register: registerType +} as const; + +export type { TypeDef as TypeHandler }; +export type TypedValue = { readonly __type: string; readonly value: unknown }; diff --git a/packages/core/src/ECS/Serialization/index.ts b/packages/core/src/ECS/Serialization/index.ts index 1db3e54b..24ed5097 100644 --- a/packages/core/src/ECS/Serialization/index.ts +++ b/packages/core/src/ECS/Serialization/index.ts @@ -24,6 +24,10 @@ export type { SerializationMetadata } from './SerializationDecorators'; +// 值序列化器 +export { ValueSerializer } from './ValueSerializer'; +export type { SerializableValue, TypeHandler, TypedValue } from './ValueSerializer'; + // 组件序列化器 export { ComponentSerializer } from './ComponentSerializer'; export type { SerializedComponent } from './ComponentSerializer'; diff --git a/packages/engine-shared/Cargo.lock b/packages/engine-shared/Cargo.lock new file mode 100644 index 00000000..ffc56733 --- /dev/null +++ b/packages/engine-shared/Cargo.lock @@ -0,0 +1,127 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "es-engine-shared" +version = "0.1.0" +dependencies = [ + "bytemuck", + "glam", + "serde", + "thiserror", +] + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/packages/engine-shared/Cargo.toml b/packages/engine-shared/Cargo.toml new file mode 100644 index 00000000..b167ccef --- /dev/null +++ b/packages/engine-shared/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "es-engine-shared" +version = "0.1.0" +edition = "2021" +authors = ["ESEngine Team"] +description = "Shared types and traits for ESEngine graphics backends | ESEngine 图形后端共享类型和 trait" +license = "MIT" +repository = "https://github.com/esengine/esengine" +keywords = ["game-engine", "graphics", "abstraction"] +categories = ["game-engines", "graphics"] + +[lib] +crate-type = ["rlib"] + +[features] +default = [] +# 启用 serde 序列化支持 | Enable serde serialization support +serde = ["dep:serde"] + +[dependencies] +# 数学库 | Math library +glam = { version = "0.24", features = ["bytemuck"] } + +# 错误处理 | Error handling +thiserror = "1.0" + +# 可选:序列化 | Optional: serialization +serde = { version = "1.0", features = ["derive"], optional = true } + +# 字节操作 | Byte manipulation +bytemuck = { version = "1.14", features = ["derive"] } + +[dev-dependencies] diff --git a/packages/engine-shared/src/batch/mod.rs b/packages/engine-shared/src/batch/mod.rs new file mode 100644 index 00000000..de218606 --- /dev/null +++ b/packages/engine-shared/src/batch/mod.rs @@ -0,0 +1,7 @@ +//! 批处理数据结构 +//! +//! Batch data structures. + +mod sprite_data; + +pub use sprite_data::*; diff --git a/packages/engine-shared/src/batch/sprite_data.rs b/packages/engine-shared/src/batch/sprite_data.rs new file mode 100644 index 00000000..3558b731 --- /dev/null +++ b/packages/engine-shared/src/batch/sprite_data.rs @@ -0,0 +1,417 @@ +//! 精灵批处理数据结构 +//! +//! Sprite batch data structures. +//! +//! 本模块提供纯数据结构,不包含任何渲染调用。 +//! This module provides pure data structures without any rendering calls. + +use crate::types::vertex::SpriteVertex; + +/// 批处理键 +/// +/// 用于区分不同批次(按材质和纹理分组)。 +/// +/// Batch key. +/// Used to distinguish different batches (grouped by material and texture). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BatchKey { + /// 材质 ID | Material ID + pub material_id: u32, + /// 纹理 ID | Texture ID + pub texture_id: u32, +} + +impl BatchKey { + /// 创建新的批处理键 + /// + /// Create new batch key. + pub const fn new(material_id: u32, texture_id: u32) -> Self { + Self { material_id, texture_id } + } + + /// 默认批处理键(默认材质和纹理) + /// + /// Default batch key (default material and texture). + pub const fn default_key() -> Self { + Self::new(0, 0) + } +} + +impl Default for BatchKey { + fn default() -> Self { + Self::default_key() + } +} + +/// 精灵批处理数据(纯数据,无渲染调用) +/// +/// 预分配的数组用于存储精灵顶点数据,避免每帧分配。 +/// +/// Sprite batch data (pure data, no rendering calls). +/// Pre-allocated arrays for storing sprite vertex data, avoiding per-frame allocation. +#[derive(Debug)] +pub struct SpriteBatchBuffer { + /// 顶点数据 | Vertex data + vertices: Vec, + + /// 索引数据 | Index data + indices: Vec, + + /// 批次列表:(BatchKey, 起始索引, 索引数量) | Batch list: (BatchKey, start index, index count) + batches: Vec<(BatchKey, u32, u32)>, + + /// 最大精灵数 | Max sprite count + max_sprites: usize, + + /// 当前精灵数 | Current sprite count + sprite_count: usize, + + /// 上一个批处理键 | Last batch key + last_batch_key: Option, +} + +impl SpriteBatchBuffer { + /// 每个精灵的顶点数 | Vertices per sprite + pub const VERTICES_PER_SPRITE: usize = 4; + + /// 每个精灵的索引数 | Indices per sprite + pub const INDICES_PER_SPRITE: usize = 6; + + /// 创建新的批处理缓冲区 + /// + /// Create new batch buffer. + pub fn new(max_sprites: usize) -> Self { + let max_vertices = max_sprites * Self::VERTICES_PER_SPRITE; + let max_indices = max_sprites * Self::INDICES_PER_SPRITE; + + // 预生成索引 + let mut indices = Vec::with_capacity(max_indices); + for i in 0..max_sprites { + let base = (i * Self::VERTICES_PER_SPRITE) as u16; + indices.extend_from_slice(&[ + base, + base + 1, + base + 2, + base + 2, + base + 3, + base, + ]); + } + + Self { + vertices: Vec::with_capacity(max_vertices), + indices, + batches: Vec::with_capacity(64), + max_sprites, + sprite_count: 0, + last_batch_key: None, + } + } + + /// 清空缓冲区(为下一帧准备) + /// + /// Clear buffer (prepare for next frame). + pub fn clear(&mut self) { + self.vertices.clear(); + self.batches.clear(); + self.sprite_count = 0; + self.last_batch_key = None; + } + + /// 添加精灵 + /// + /// Add sprite. + /// + /// # Parameters + /// + /// - `x`, `y`: 位置 | Position + /// - `width`, `height`: 尺寸 | Size + /// - `rotation`: 旋转(弧度)| Rotation (radians) + /// - `origin_x`, `origin_y`: 原点(0-1)| Origin (0-1) + /// - `u0`, `v0`, `u1`, `v1`: UV 坐标 | UV coordinates + /// - `color`: 打包的 RGBA 颜色 | Packed RGBA color + /// - `texture_id`: 纹理 ID | Texture ID + /// - `material_id`: 材质 ID | Material ID + #[allow(clippy::too_many_arguments)] + pub fn add_sprite( + &mut self, + x: f32, + y: f32, + width: f32, + height: f32, + rotation: f32, + origin_x: f32, + origin_y: f32, + u0: f32, + v0: f32, + u1: f32, + v1: f32, + color: u32, + texture_id: u32, + material_id: u32, + ) -> bool { + if self.sprite_count >= self.max_sprites { + return false; + } + + // 解包颜色 + let r = ((color >> 24) & 0xFF) as f32 / 255.0; + let g = ((color >> 16) & 0xFF) as f32 / 255.0; + let b = ((color >> 8) & 0xFF) as f32 / 255.0; + let a = (color & 0xFF) as f32 / 255.0; + let color_arr = [r, g, b, a]; + + // 计算宽高比 + let aspect = if height != 0.0 { width / height } else { 1.0 }; + + // 计算顶点位置(考虑原点和旋转) + let ox = origin_x * width; + let oy = origin_y * height; + + let cos_r = rotation.cos(); + let sin_r = rotation.sin(); + + // 四个角的局部坐标 + let corners = [ + (-ox, -oy), // 左上 + (width - ox, -oy), // 右上 + (width - ox, height - oy), // 右下 + (-ox, height - oy), // 左下 + ]; + + // UV 坐标 + let uvs = [ + [u0, v0], // 左上 + [u1, v0], // 右上 + [u1, v1], // 右下 + [u0, v1], // 左下 + ]; + + // 添加四个顶点 + for i in 0..4 { + let (lx, ly) = corners[i]; + let rx = lx * cos_r - ly * sin_r + x; + let ry = lx * sin_r + ly * cos_r + y; + + self.vertices.push(SpriteVertex { + position: [rx, ry], + texcoord: uvs[i], + color: color_arr, + aspect, + }); + } + + // 更新批次 + let key = BatchKey::new(material_id, texture_id); + if self.last_batch_key != Some(key) { + // 开始新批次 + let start_index = (self.sprite_count * Self::INDICES_PER_SPRITE) as u32; + self.batches.push((key, start_index, Self::INDICES_PER_SPRITE as u32)); + self.last_batch_key = Some(key); + } else { + // 扩展当前批次 + if let Some((_, _, count)) = self.batches.last_mut() { + *count += Self::INDICES_PER_SPRITE as u32; + } + } + + self.sprite_count += 1; + true + } + + /// 从 SoA 数据添加精灵(与现有 API 兼容) + /// + /// Add sprites from SoA data (compatible with existing API). + /// + /// # Parameters + /// + /// - `transforms`: [x, y, rotation, scaleX, scaleY, originX, originY] per sprite + /// - `texture_ids`: 纹理 ID | Texture IDs + /// - `uvs`: [u0, v0, u1, v1] per sprite + /// - `colors`: 打包的 RGBA 颜色 | Packed RGBA colors + /// - `material_ids`: 材质 ID | Material IDs + /// - `texture_sizes`: 纹理尺寸映射函数 | Texture size lookup function + pub fn add_sprites_soa( + &mut self, + transforms: &[f32], + texture_ids: &[u32], + uvs: &[f32], + colors: &[u32], + material_ids: &[u32], + texture_sizes: F, + ) -> usize + where + F: Fn(u32) -> (f32, f32), + { + let count = texture_ids.len(); + let mut added = 0; + + for i in 0..count { + let t_offset = i * 7; + let uv_offset = i * 4; + + if t_offset + 6 >= transforms.len() || uv_offset + 3 >= uvs.len() { + break; + } + + let x = transforms[t_offset]; + let y = transforms[t_offset + 1]; + let rotation = transforms[t_offset + 2]; + let scale_x = transforms[t_offset + 3]; + let scale_y = transforms[t_offset + 4]; + let origin_x = transforms[t_offset + 5]; + let origin_y = transforms[t_offset + 6]; + + let texture_id = texture_ids[i]; + let (tex_width, tex_height) = texture_sizes(texture_id); + + let width = tex_width * scale_x; + let height = tex_height * scale_y; + + let u0 = uvs[uv_offset]; + let v0 = uvs[uv_offset + 1]; + let u1 = uvs[uv_offset + 2]; + let v1 = uvs[uv_offset + 3]; + + let color = colors[i]; + let material_id = material_ids[i]; + + if self.add_sprite( + x, y, width, height, rotation, origin_x, origin_y, + u0, v0, u1, v1, color, texture_id, material_id, + ) { + added += 1; + } else { + break; + } + } + + added + } + + /// 获取顶点数据 + /// + /// Get vertex data. + pub fn vertices(&self) -> &[SpriteVertex] { + &self.vertices + } + + /// 获取顶点数据(字节) + /// + /// Get vertex data as bytes. + pub fn vertices_as_bytes(&self) -> &[u8] { + bytemuck::cast_slice(&self.vertices) + } + + /// 获取索引数据 + /// + /// Get index data. + pub fn indices(&self) -> &[u16] { + &self.indices[..self.sprite_count * Self::INDICES_PER_SPRITE] + } + + /// 获取批次列表 + /// + /// Get batch list. + pub fn batches(&self) -> &[(BatchKey, u32, u32)] { + &self.batches + } + + /// 获取精灵数量 + /// + /// Get sprite count. + pub fn sprite_count(&self) -> usize { + self.sprite_count + } + + /// 获取最大精灵数 + /// + /// Get max sprite count. + pub fn max_sprites(&self) -> usize { + self.max_sprites + } + + /// 是否为空 + /// + /// Check if empty. + pub fn is_empty(&self) -> bool { + self.sprite_count == 0 + } + + /// 是否已满 + /// + /// Check if full. + pub fn is_full(&self) -> bool { + self.sprite_count >= self.max_sprites + } +} + +impl Default for SpriteBatchBuffer { + fn default() -> Self { + Self::new(10000) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_batch_buffer_creation() { + let buffer = SpriteBatchBuffer::new(100); + assert_eq!(buffer.max_sprites(), 100); + assert_eq!(buffer.sprite_count(), 0); + assert!(buffer.is_empty()); + } + + #[test] + fn test_add_sprite() { + let mut buffer = SpriteBatchBuffer::new(100); + + let result = buffer.add_sprite( + 100.0, 100.0, // position + 64.0, 64.0, // size + 0.0, // rotation + 0.5, 0.5, // origin + 0.0, 0.0, 1.0, 1.0, // uvs + 0xFFFFFFFF, // color (white) + 1, // texture_id + 0, // material_id + ); + + assert!(result); + assert_eq!(buffer.sprite_count(), 1); + assert_eq!(buffer.vertices().len(), 4); + assert_eq!(buffer.batches().len(), 1); + } + + #[test] + fn test_batch_grouping() { + let mut buffer = SpriteBatchBuffer::new(100); + + // 添加两个相同纹理/材质的精灵(应该合并到一个批次) + buffer.add_sprite(0.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0); + buffer.add_sprite(50.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0); + + assert_eq!(buffer.batches().len(), 1); + assert_eq!(buffer.batches()[0].2, 12); // 2 sprites * 6 indices + + // 添加不同纹理的精灵(应该创建新批次) + buffer.add_sprite(100.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 2, 0); + + assert_eq!(buffer.batches().len(), 2); + } + + #[test] + fn test_clear() { + let mut buffer = SpriteBatchBuffer::new(100); + + buffer.add_sprite(0.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0xFFFFFFFF, 1, 0); + assert_eq!(buffer.sprite_count(), 1); + + buffer.clear(); + assert_eq!(buffer.sprite_count(), 0); + assert!(buffer.is_empty()); + assert!(buffer.batches().is_empty()); + } +} diff --git a/packages/engine-shared/src/camera.rs b/packages/engine-shared/src/camera.rs new file mode 100644 index 00000000..ed0b4b91 --- /dev/null +++ b/packages/engine-shared/src/camera.rs @@ -0,0 +1,421 @@ +//! 2D 相机(纯数学实现) +//! +//! 2D camera (pure math implementation). + +use glam::{Mat3, Vec2}; + +/// 2D 相机 +/// +/// 提供正交投影、坐标转换等功能。 +/// 纯数学实现,不依赖任何图形 API。 +/// +/// 2D camera. +/// Provides orthographic projection, coordinate conversion, etc. +/// Pure math implementation, no graphics API dependencies. +#[derive(Debug, Clone, Copy)] +pub struct Camera2D { + /// 相机位置(世界坐标)| Camera position (world coordinates) + position: Vec2, + + /// 旋转角度(弧度,顺时针为正)| Rotation (radians, clockwise positive) + rotation: f32, + + /// 缩放级别 | Zoom level + zoom: f32, + + /// 视口宽度 | Viewport width + width: f32, + + /// 视口高度 | Viewport height + height: f32, +} + +impl Default for Camera2D { + fn default() -> Self { + Self { + position: Vec2::ZERO, + rotation: 0.0, + zoom: 1.0, + width: 800.0, + height: 600.0, + } + } +} + +impl Camera2D { + /// 创建新相机 + /// + /// Create new camera. + pub fn new(width: f32, height: f32) -> Self { + Self { + width, + height, + ..Default::default() + } + } + + // ==================== Builder Pattern ==================== + + /// 设置位置 + /// + /// Set position. + pub fn with_position(mut self, x: f32, y: f32) -> Self { + self.position = Vec2::new(x, y); + self + } + + /// 设置缩放 + /// + /// Set zoom. + pub fn with_zoom(mut self, zoom: f32) -> Self { + self.zoom = zoom.max(0.001); + self + } + + /// 设置旋转 + /// + /// Set rotation. + pub fn with_rotation(mut self, rotation: f32) -> Self { + self.rotation = rotation; + self + } + + // ==================== Getters ==================== + + /// 获取位置 + /// + /// Get position. + pub fn position(&self) -> Vec2 { + self.position + } + + /// 获取 X 坐标 + /// + /// Get X coordinate. + pub fn x(&self) -> f32 { + self.position.x + } + + /// 获取 Y 坐标 + /// + /// Get Y coordinate. + pub fn y(&self) -> f32 { + self.position.y + } + + /// 获取旋转 + /// + /// Get rotation. + pub fn rotation(&self) -> f32 { + self.rotation + } + + /// 获取缩放 + /// + /// Get zoom. + pub fn zoom(&self) -> f32 { + self.zoom + } + + /// 获取视口宽度 + /// + /// Get viewport width. + pub fn width(&self) -> f32 { + self.width + } + + /// 获取视口高度 + /// + /// Get viewport height. + pub fn height(&self) -> f32 { + self.height + } + + // ==================== Setters ==================== + + /// 设置位置 + /// + /// Set position. + pub fn set_position(&mut self, x: f32, y: f32) { + self.position = Vec2::new(x, y); + } + + /// 设置旋转 + /// + /// Set rotation. + pub fn set_rotation(&mut self, rotation: f32) { + self.rotation = rotation; + } + + /// 设置缩放 + /// + /// Set zoom. + pub fn set_zoom(&mut self, zoom: f32) { + self.zoom = zoom.max(0.001); + } + + /// 调整视口大小 + /// + /// Resize viewport. + pub fn resize(&mut self, width: f32, height: f32) { + self.width = width; + self.height = height; + } + + // ==================== Transform Methods ==================== + + /// 移动相机 + /// + /// Move camera. + pub fn translate(&mut self, dx: f32, dy: f32) { + self.position.x += dx; + self.position.y += dy; + } + + /// 旋转相机 + /// + /// Rotate camera. + pub fn rotate(&mut self, delta: f32) { + self.rotation += delta; + } + + /// 缩放相机 + /// + /// Zoom camera. + pub fn zoom_by(&mut self, factor: f32) { + self.zoom = (self.zoom * factor).max(0.001); + } + + // ==================== Matrix Generation ==================== + + /// 获取投影矩阵 + /// + /// 将世界坐标转换为 NDC(-1 到 1)。 + /// + /// Get projection matrix. + /// Transforms world coordinates to NDC (-1 to 1). + pub fn projection_matrix(&self) -> Mat3 { + // 计算缩放 + let scale_x = 2.0 / self.width * self.zoom; + let scale_y = 2.0 / self.height * self.zoom; + + // 计算旋转 + let cos_r = self.rotation.cos(); + let sin_r = self.rotation.sin(); + + // 计算平移(相机位置取反) + let tx = -self.position.x; + let ty = -self.position.y; + + // 构建变换矩阵:Scale * Rotate * Translate + // 先平移,再旋转,最后缩放 + Mat3::from_cols_array(&[ + scale_x * cos_r, + scale_y * sin_r, + 0.0, + -scale_x * sin_r, + scale_y * cos_r, + 0.0, + scale_x * (tx * cos_r - ty * sin_r), + scale_y * (tx * sin_r + ty * cos_r), + 1.0, + ]) + } + + /// 获取视图矩阵 + /// + /// Get view matrix. + pub fn view_matrix(&self) -> Mat3 { + let cos_r = self.rotation.cos(); + let sin_r = self.rotation.sin(); + + let tx = -self.position.x; + let ty = -self.position.y; + + Mat3::from_cols_array(&[ + cos_r, + sin_r, + 0.0, + -sin_r, + cos_r, + 0.0, + tx * cos_r - ty * sin_r, + tx * sin_r + ty * cos_r, + 1.0, + ]) + } + + /// 获取逆投影矩阵 + /// + /// Get inverse projection matrix. + pub fn inverse_projection_matrix(&self) -> Mat3 { + self.projection_matrix().inverse() + } + + // ==================== Coordinate Conversion ==================== + + /// 屏幕坐标转世界坐标 + /// + /// Screen to world coordinates. + /// + /// # Parameters + /// + /// - `screen_pos`: 屏幕坐标(像素,左上角为原点)| Screen coordinates (pixels, origin at top-left) + /// + /// # Returns + /// + /// 世界坐标 | World coordinates + pub fn screen_to_world(&self, screen_pos: Vec2) -> Vec2 { + // 屏幕坐标转 NDC + let ndc_x = (screen_pos.x / self.width) * 2.0 - 1.0; + let ndc_y = 1.0 - (screen_pos.y / self.height) * 2.0; // Y 轴翻转 + + // NDC 转世界坐标 + let inv_proj = self.inverse_projection_matrix(); + let world = inv_proj * glam::Vec3::new(ndc_x, ndc_y, 1.0); + + Vec2::new(world.x, world.y) + } + + /// 世界坐标转屏幕坐标 + /// + /// World to screen coordinates. + /// + /// # Parameters + /// + /// - `world_pos`: 世界坐标 | World coordinates + /// + /// # Returns + /// + /// 屏幕坐标(像素,左上角为原点)| Screen coordinates (pixels, origin at top-left) + pub fn world_to_screen(&self, world_pos: Vec2) -> Vec2 { + // 世界坐标转 NDC + let proj = self.projection_matrix(); + let ndc = proj * glam::Vec3::new(world_pos.x, world_pos.y, 1.0); + + // NDC 转屏幕坐标 + let screen_x = (ndc.x + 1.0) * 0.5 * self.width; + let screen_y = (1.0 - ndc.y) * 0.5 * self.height; // Y 轴翻转 + + Vec2::new(screen_x, screen_y) + } + + /// 获取可见区域(世界坐标 AABB) + /// + /// Get visible bounds (world coordinate AABB). + pub fn visible_bounds(&self) -> (Vec2, Vec2) { + // 四个角的屏幕坐标 + let corners = [ + Vec2::new(0.0, 0.0), + Vec2::new(self.width, 0.0), + Vec2::new(self.width, self.height), + Vec2::new(0.0, self.height), + ]; + + // 转换为世界坐标 + let world_corners: Vec = corners.iter().map(|c| self.screen_to_world(*c)).collect(); + + // 计算 AABB + let mut min = world_corners[0]; + let mut max = world_corners[0]; + + for corner in &world_corners[1..] { + min = min.min(*corner); + max = max.max(*corner); + } + + (min, max) + } + + /// 检查点是否在可见区域内 + /// + /// Check if point is visible. + pub fn is_point_visible(&self, world_pos: Vec2) -> bool { + let (min, max) = self.visible_bounds(); + world_pos.x >= min.x && world_pos.x <= max.x && world_pos.y >= min.y && world_pos.y <= max.y + } + + /// 检查矩形是否与可见区域相交 + /// + /// Check if rectangle intersects visible area. + pub fn is_rect_visible(&self, pos: Vec2, size: Vec2) -> bool { + let (min, max) = self.visible_bounds(); + let rect_max = pos + size; + + pos.x <= max.x && rect_max.x >= min.x && pos.y <= max.y && rect_max.y >= min.y + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_camera_creation() { + let camera = Camera2D::new(800.0, 600.0); + assert_eq!(camera.width(), 800.0); + assert_eq!(camera.height(), 600.0); + assert_eq!(camera.position(), Vec2::ZERO); + assert_eq!(camera.zoom(), 1.0); + assert_eq!(camera.rotation(), 0.0); + } + + #[test] + fn test_camera_builder() { + let camera = Camera2D::new(800.0, 600.0) + .with_position(100.0, 50.0) + .with_zoom(2.0) + .with_rotation(std::f32::consts::PI / 4.0); + + assert_eq!(camera.position(), Vec2::new(100.0, 50.0)); + assert_eq!(camera.zoom(), 2.0); + assert!((camera.rotation() - std::f32::consts::PI / 4.0).abs() < 0.0001); + } + + #[test] + fn test_screen_to_world_identity() { + let camera = Camera2D::new(800.0, 600.0); + + // 屏幕中心应该对应世界原点 + let center = camera.screen_to_world(Vec2::new(400.0, 300.0)); + assert!((center.x).abs() < 0.001); + assert!((center.y).abs() < 0.001); + } + + #[test] + fn test_world_to_screen_identity() { + let camera = Camera2D::new(800.0, 600.0); + + // 世界原点应该对应屏幕中心 + let center = camera.world_to_screen(Vec2::ZERO); + assert!((center.x - 400.0).abs() < 0.001); + assert!((center.y - 300.0).abs() < 0.001); + } + + #[test] + fn test_coordinate_roundtrip() { + let camera = Camera2D::new(800.0, 600.0) + .with_position(100.0, 50.0) + .with_zoom(1.5) + .with_rotation(0.3); + + let world_pos = Vec2::new(200.0, 150.0); + let screen_pos = camera.world_to_screen(world_pos); + let back_to_world = camera.screen_to_world(screen_pos); + + assert!((back_to_world.x - world_pos.x).abs() < 0.01); + assert!((back_to_world.y - world_pos.y).abs() < 0.01); + } + + #[test] + fn test_visible_bounds() { + let camera = Camera2D::new(800.0, 600.0); + let (min, max) = camera.visible_bounds(); + + // 默认相机应该看到 -400 到 400(水平),-300 到 300(垂直) + assert!((min.x - (-400.0)).abs() < 0.01); + assert!((max.x - 400.0).abs() < 0.01); + assert!((min.y - (-300.0)).abs() < 0.01); + assert!((max.y - 300.0).abs() < 0.01); + } +} diff --git a/packages/engine-shared/src/lib.rs b/packages/engine-shared/src/lib.rs new file mode 100644 index 00000000..ceb4bd6f --- /dev/null +++ b/packages/engine-shared/src/lib.rs @@ -0,0 +1,40 @@ +//! ESEngine 图形后端共享库 +//! +//! 本库提供跨平台图形后端抽象层,包括: +//! - 类型安全的资源句柄 +//! - 图形后端 trait 定义 +//! - 平台抽象 trait +//! - 共享数据结构 +//! +//! ESEngine graphics backend shared library. +//! Provides cross-platform graphics backend abstraction including: +//! - Type-safe resource handles +//! - Graphics backend trait definitions +//! - Platform abstraction traits +//! - Shared data structures + +pub mod types; +pub mod traits; +pub mod batch; +pub mod camera; + +// Re-export commonly used items | 重新导出常用项 +pub use types::{ + handle::*, + vertex::*, + blend::*, + uniform::*, + texture::*, +}; + +pub use traits::{ + backend::*, + platform::*, + renderer::*, +}; + +pub use batch::*; +pub use camera::*; + +// Re-export glam for convenience | 方便使用,重新导出 glam +pub use glam::{Vec2, Vec3, Vec4, Mat3, Mat4}; diff --git a/packages/engine-shared/src/traits/backend.rs b/packages/engine-shared/src/traits/backend.rs new file mode 100644 index 00000000..61a0aa7e --- /dev/null +++ b/packages/engine-shared/src/traits/backend.rs @@ -0,0 +1,519 @@ +//! 图形后端主 trait +//! +//! Main graphics backend trait. + +use crate::types::{ + handle::*, + vertex::*, + blend::*, + texture::*, + uniform::UniformValue, +}; +use glam::{Vec2, Vec3, Vec4, Mat3, Mat4}; +use thiserror::Error; + +// ==================== 错误类型 | Error Types ==================== + +/// 图形后端错误 +/// +/// Graphics backend error. +#[derive(Debug, Error)] +pub enum GraphicsError { + /// 着色器编译失败 | Shader compilation failed + #[error("Shader compilation failed: {0}")] + ShaderCompilation(String), + + /// 着色器链接失败 | Shader linking failed + #[error("Shader linking failed: {0}")] + ShaderLinking(String), + + /// 纹理创建失败 | Texture creation failed + #[error("Texture creation failed: {0}")] + TextureCreation(String), + + /// 缓冲区创建失败 | Buffer creation failed + #[error("Buffer creation failed: {0}")] + BufferCreation(String), + + /// 无效句柄 | Invalid handle + #[error("Invalid handle: {0}")] + InvalidHandle(String), + + /// 上下文丢失 | Context lost + #[error("Context lost")] + ContextLost, + + /// 不支持的操作 | Unsupported operation + #[error("Unsupported operation: {0}")] + Unsupported(String), + + /// 后端错误 | Backend error + #[error("Backend error: {0}")] + Backend(String), + + /// 资源不存在 | Resource not found + #[error("Resource not found: {0}")] + ResourceNotFound(String), + + /// 数据大小不匹配 | Data size mismatch + #[error("Data size mismatch: expected {expected}, got {actual}")] + DataSizeMismatch { expected: usize, actual: usize }, +} + +/// 图形操作结果 +/// +/// Graphics operation result. +pub type GraphicsResult = Result; + +// ==================== 缓冲区用途 | Buffer Usage ==================== + +/// 缓冲区用途 +/// +/// Buffer usage. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum BufferUsage { + /// 静态数据(不常更新)| Static data (rarely updated) + #[default] + Static, + /// 动态数据(经常更新)| Dynamic data (frequently updated) + Dynamic, + /// 流式数据(每帧更新)| Streaming data (updated every frame) + Stream, +} + +// ==================== 图形功能 | Graphics Features ==================== + +/// 图形功能 +/// +/// Graphics feature. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GraphicsFeature { + /// 各向异性过滤 | Anisotropic filtering + AnisotropicFiltering, + /// 实例化渲染 | Instanced rendering + Instancing, + /// 计算着色器 | Compute shaders + ComputeShaders, + /// 多渲染目标 | Multiple render targets + MultipleRenderTargets, + /// 浮点纹理 | Float textures + FloatTextures, + /// WebGPU | WebGPU support + WebGPU, +} + +// ==================== 图形后端 Trait | Graphics Backend Trait ==================== + +/// 图形后端主 trait +/// +/// 定义所有图形操作的抽象接口,由具体后端(WebGL2、WGPU 等)实现。 +/// +/// Main graphics backend trait. +/// Defines abstract interface for all graphics operations, +/// implemented by concrete backends (WebGL2, WGPU, etc.). +pub trait GraphicsBackend: Sized { + // ==================== 基本信息 | Basic Info ==================== + + /// 后端名称 + /// + /// Backend name. + fn name(&self) -> &'static str; + + /// 后端版本 + /// + /// Backend version. + fn version(&self) -> &str; + + // ==================== 生命周期 | Lifecycle ==================== + + /// 调整视口大小 + /// + /// Resize viewport. + fn resize(&mut self, width: u32, height: u32); + + /// 获取当前宽度 + /// + /// Get current width. + fn width(&self) -> u32; + + /// 获取当前高度 + /// + /// Get current height. + fn height(&self) -> u32; + + // ==================== 帧控制 | Frame Control ==================== + + /// 开始新帧 + /// + /// Begin new frame. + fn begin_frame(&mut self); + + /// 结束当前帧 + /// + /// End current frame. + fn end_frame(&mut self); + + /// 清屏 + /// + /// Clear screen. + fn clear(&mut self, r: f32, g: f32, b: f32, a: f32); + + /// 设置视口 + /// + /// Set viewport. + fn set_viewport(&mut self, x: i32, y: i32, width: u32, height: u32); + + // ==================== 缓冲区操作 | Buffer Operations ==================== + + /// 创建顶点缓冲区 + /// + /// Create vertex buffer. + fn create_vertex_buffer( + &mut self, + data: &[u8], + usage: BufferUsage, + ) -> GraphicsResult; + + /// 创建指定大小的顶点缓冲区(预分配) + /// + /// Create vertex buffer with specified size (pre-allocate). + fn create_vertex_buffer_sized( + &mut self, + size: usize, + usage: BufferUsage, + ) -> GraphicsResult; + + /// 创建索引缓冲区 + /// + /// Create index buffer. + fn create_index_buffer( + &mut self, + data: &[u16], + usage: BufferUsage, + ) -> GraphicsResult; + + /// 创建索引缓冲区(u32) + /// + /// Create index buffer (u32). + fn create_index_buffer_u32( + &mut self, + data: &[u32], + usage: BufferUsage, + ) -> GraphicsResult; + + /// 更新缓冲区数据 + /// + /// Update buffer data. + fn update_buffer( + &mut self, + handle: BufferHandle, + offset: usize, + data: &[u8], + ) -> GraphicsResult<()>; + + /// 销毁缓冲区 + /// + /// Destroy buffer. + fn destroy_buffer(&mut self, handle: BufferHandle); + + /// 创建顶点数组对象 + /// + /// Create vertex array object. + fn create_vertex_array( + &mut self, + vertex_buffer: BufferHandle, + index_buffer: Option, + layout: &VertexLayout, + ) -> GraphicsResult; + + /// 销毁顶点数组对象 + /// + /// Destroy vertex array object. + fn destroy_vertex_array(&mut self, handle: VertexArrayHandle); + + // ==================== 着色器操作 | Shader Operations ==================== + + /// 编译着色器程序 + /// + /// Compile shader program. + fn compile_shader( + &mut self, + vertex_src: &str, + fragment_src: &str, + ) -> GraphicsResult; + + /// 销毁着色器 + /// + /// Destroy shader. + fn destroy_shader(&mut self, handle: ShaderHandle); + + /// 绑定着色器 + /// + /// Bind shader. + fn bind_shader(&mut self, handle: ShaderHandle) -> GraphicsResult<()>; + + /// 设置 Uniform(float) + /// + /// Set uniform (float). + fn set_uniform_f32(&mut self, name: &str, value: f32) -> GraphicsResult<()>; + + /// 设置 Uniform(vec2) + /// + /// Set uniform (vec2). + fn set_uniform_vec2(&mut self, name: &str, value: Vec2) -> GraphicsResult<()>; + + /// 设置 Uniform(vec3) + /// + /// Set uniform (vec3). + fn set_uniform_vec3(&mut self, name: &str, value: Vec3) -> GraphicsResult<()>; + + /// 设置 Uniform(vec4) + /// + /// Set uniform (vec4). + fn set_uniform_vec4(&mut self, name: &str, value: Vec4) -> GraphicsResult<()>; + + /// 设置 Uniform(mat3) + /// + /// Set uniform (mat3). + fn set_uniform_mat3(&mut self, name: &str, value: &Mat3) -> GraphicsResult<()>; + + /// 设置 Uniform(mat4) + /// + /// Set uniform (mat4). + fn set_uniform_mat4(&mut self, name: &str, value: &Mat4) -> GraphicsResult<()>; + + /// 设置 Uniform(int/sampler) + /// + /// Set uniform (int/sampler). + fn set_uniform_i32(&mut self, name: &str, value: i32) -> GraphicsResult<()>; + + /// 设置 Uniform(通用) + /// + /// Set uniform (generic). + fn set_uniform(&mut self, name: &str, value: &UniformValue) -> GraphicsResult<()> { + match value { + UniformValue::Float(v) => self.set_uniform_f32(name, *v), + UniformValue::Float2(v) => self.set_uniform_vec2(name, *v), + UniformValue::Float3(v) => self.set_uniform_vec3(name, *v), + UniformValue::Float4(v) => self.set_uniform_vec4(name, *v), + UniformValue::Int(v) => self.set_uniform_i32(name, *v), + UniformValue::Mat3(v) => self.set_uniform_mat3(name, v), + UniformValue::Mat4(v) => self.set_uniform_mat4(name, v), + UniformValue::Texture(unit) => self.set_uniform_i32(name, *unit as i32), + _ => Err(GraphicsError::Unsupported(format!( + "Uniform type {} not supported", + value.type_name() + ))), + } + } + + // ==================== 纹理操作 | Texture Operations ==================== + + /// 创建纹理 + /// + /// Create texture. + fn create_texture(&mut self, desc: &TextureDescriptor) -> GraphicsResult; + + /// 创建空白纹理(用于动态图集) + /// + /// Create blank texture (for dynamic atlas). + fn create_blank_texture(&mut self, width: u32, height: u32) -> GraphicsResult; + + /// 上传纹理数据 + /// + /// Upload texture data. + fn upload_texture_data( + &mut self, + handle: TextureHandle, + data: &[u8], + width: u32, + height: u32, + ) -> GraphicsResult<()>; + + /// 更新纹理区域 + /// + /// Update texture region. + fn update_texture_region( + &mut self, + handle: TextureHandle, + x: u32, + y: u32, + width: u32, + height: u32, + data: &[u8], + ) -> GraphicsResult<()>; + + /// 销毁纹理 + /// + /// Destroy texture. + fn destroy_texture(&mut self, handle: TextureHandle); + + /// 绑定纹理到纹理单元 + /// + /// Bind texture to texture unit. + fn bind_texture(&mut self, handle: TextureHandle, unit: u32) -> GraphicsResult<()>; + + /// 获取纹理尺寸 + /// + /// Get texture dimensions. + fn get_texture_size(&self, handle: TextureHandle) -> Option<(u32, u32)>; + + // ==================== 渲染状态 | Render State ==================== + + /// 应用渲染状态 + /// + /// Apply render state. + fn apply_render_state(&mut self, state: &RenderState); + + /// 设置混合模式 + /// + /// Set blend mode. + fn set_blend_mode(&mut self, mode: BlendMode); + + /// 设置裁剪矩形 + /// + /// Set scissor rectangle. + fn set_scissor(&mut self, rect: Option); + + // ==================== 绘制命令 | Draw Commands ==================== + + /// 绘制(索引,u16) + /// + /// Draw indexed (u16). + fn draw_indexed( + &mut self, + vao: VertexArrayHandle, + index_count: u32, + index_offset: u32, + ) -> GraphicsResult<()>; + + /// 绘制(索引,u32) + /// + /// Draw indexed (u32). + fn draw_indexed_u32( + &mut self, + vao: VertexArrayHandle, + index_count: u32, + index_offset: u32, + ) -> GraphicsResult<()> { + // 默认实现使用 u16 版本,后端可覆盖 + self.draw_indexed(vao, index_count, index_offset) + } + + /// 绘制(非索引) + /// + /// Draw non-indexed. + fn draw( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()>; + + /// 绘制线段 + /// + /// Draw lines. + fn draw_lines( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()>; + + /// 绘制闭合线条 + /// + /// Draw line loop. + fn draw_line_loop( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()>; + + /// 绘制连续线条 + /// + /// Draw line strip. + fn draw_line_strip( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()>; + + // ==================== 查询 | Queries ==================== + + /// 获取最大纹理尺寸 + /// + /// Get max texture size. + fn max_texture_size(&self) -> u32; + + /// 是否支持某功能 + /// + /// Check feature support. + fn supports_feature(&self, feature: GraphicsFeature) -> bool; + + /// 获取最大纹理单元数 + /// + /// Get max texture units. + fn max_texture_units(&self) -> u32 { + 16 // 默认值,后端可覆盖 + } + + /// 获取最大顶点属性数 + /// + /// Get max vertex attributes. + fn max_vertex_attributes(&self) -> u32 { + 16 // 默认值,后端可覆盖 + } +} + +// ==================== 扩展 Trait | Extension Traits ==================== + +/// 帧缓冲区操作扩展 +/// +/// Framebuffer operations extension. +pub trait FramebufferExt: GraphicsBackend { + /// 创建帧缓冲区 + /// + /// Create framebuffer. + fn create_framebuffer( + &mut self, + color_attachment: TextureHandle, + depth_attachment: Option, + ) -> GraphicsResult; + + /// 销毁帧缓冲区 + /// + /// Destroy framebuffer. + fn destroy_framebuffer(&mut self, handle: FramebufferHandle); + + /// 绑定帧缓冲区 + /// + /// Bind framebuffer. + fn bind_framebuffer(&mut self, handle: Option) -> GraphicsResult<()>; +} + +/// 实例化渲染扩展 +/// +/// Instanced rendering extension. +pub trait InstancingExt: GraphicsBackend { + /// 绘制实例化(索引) + /// + /// Draw instanced (indexed). + fn draw_indexed_instanced( + &mut self, + vao: VertexArrayHandle, + index_count: u32, + instance_count: u32, + ) -> GraphicsResult<()>; + + /// 绘制实例化(非索引) + /// + /// Draw instanced (non-indexed). + fn draw_instanced( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + instance_count: u32, + ) -> GraphicsResult<()>; +} diff --git a/packages/engine-shared/src/traits/mod.rs b/packages/engine-shared/src/traits/mod.rs new file mode 100644 index 00000000..d0e6e0ea --- /dev/null +++ b/packages/engine-shared/src/traits/mod.rs @@ -0,0 +1,7 @@ +//! 图形后端 trait 定义 +//! +//! Graphics backend trait definitions. + +pub mod backend; +pub mod platform; +pub mod renderer; diff --git a/packages/engine-shared/src/traits/platform.rs b/packages/engine-shared/src/traits/platform.rs new file mode 100644 index 00000000..4a9226ac --- /dev/null +++ b/packages/engine-shared/src/traits/platform.rs @@ -0,0 +1,441 @@ +//! 平台抽象 trait +//! +//! Platform abstraction trait. + +use super::backend::{GraphicsBackend, GraphicsResult}; +use crate::types::texture::ImageData; +use std::future::Future; + +// ==================== 后端配置 | Backend Configuration ==================== + +/// 后端配置 +/// +/// Backend configuration. +#[derive(Debug, Clone)] +pub struct BackendConfig { + /// 画布/窗口 ID + /// + /// Canvas/Window ID. + pub canvas_id: Option, + + /// 初始宽度 + /// + /// Initial width. + pub width: u32, + + /// 初始高度 + /// + /// Initial height. + pub height: u32, + + /// 是否启用抗锯齿 + /// + /// Enable antialiasing. + pub antialias: bool, + + /// 是否使用高 DPI + /// + /// Use high DPI. + pub high_dpi: bool, + + /// 电源偏好 + /// + /// Power preference. + pub power_preference: PowerPreference, + + /// 是否保留绘制缓冲区 + /// + /// Preserve drawing buffer. + pub preserve_drawing_buffer: bool, + + /// Alpha 模式 + /// + /// Alpha mode. + pub alpha: bool, + + /// 深度缓冲区大小(0 表示禁用) + /// + /// Depth buffer size (0 to disable). + pub depth_size: u8, + + /// 模板缓冲区大小(0 表示禁用) + /// + /// Stencil buffer size (0 to disable). + pub stencil_size: u8, +} + +impl Default for BackendConfig { + fn default() -> Self { + Self { + canvas_id: None, + width: 800, + height: 600, + antialias: false, + high_dpi: true, + power_preference: PowerPreference::HighPerformance, + preserve_drawing_buffer: false, + alpha: true, + depth_size: 0, + stencil_size: 0, + } + } +} + +impl BackendConfig { + /// 创建新配置 + /// + /// Create new configuration. + pub fn new(width: u32, height: u32) -> Self { + Self { + width, + height, + ..Default::default() + } + } + + /// 设置画布 ID + /// + /// Set canvas ID. + pub fn with_canvas(mut self, canvas_id: impl Into) -> Self { + self.canvas_id = Some(canvas_id.into()); + self + } + + /// 设置抗锯齿 + /// + /// Set antialiasing. + pub fn with_antialias(mut self, antialias: bool) -> Self { + self.antialias = antialias; + self + } + + /// 设置高 DPI + /// + /// Set high DPI. + pub fn with_high_dpi(mut self, high_dpi: bool) -> Self { + self.high_dpi = high_dpi; + self + } + + /// 设置电源偏好 + /// + /// Set power preference. + pub fn with_power_preference(mut self, preference: PowerPreference) -> Self { + self.power_preference = preference; + self + } + + /// 启用深度缓冲区 + /// + /// Enable depth buffer. + pub fn with_depth(mut self, bits: u8) -> Self { + self.depth_size = bits; + self + } + + /// 启用模板缓冲区 + /// + /// Enable stencil buffer. + pub fn with_stencil(mut self, bits: u8) -> Self { + self.stencil_size = bits; + self + } +} + +/// 电源偏好 +/// +/// Power preference. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum PowerPreference { + /// 低功耗(集成显卡) + /// + /// Low power (integrated GPU). + LowPower, + + /// 高性能(独立显卡,默认) + /// + /// High performance (discrete GPU, default). + #[default] + HighPerformance, +} + +// ==================== 资产加载器 | Asset Loader ==================== + +/// 资产加载错误 +/// +/// Asset loading error. +#[derive(Debug, Clone)] +pub struct AssetError { + /// 错误消息 + /// + /// Error message. + pub message: String, + + /// 资产路径 + /// + /// Asset path. + pub path: String, +} + +impl std::fmt::Display for AssetError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to load '{}': {}", self.path, self.message) + } +} + +impl std::error::Error for AssetError {} + +/// 资产加载结果 +/// +/// Asset loading result. +pub type AssetResult = Result; + +/// 资产加载器 trait +/// +/// Asset loader trait. +pub trait AssetLoader { + /// 加载二进制数据 + /// + /// Load binary data. + fn load_bytes(&self, path: &str) -> impl Future>> + Send; + + /// 加载文本 + /// + /// Load text. + fn load_text(&self, path: &str) -> impl Future> + Send; + + /// 加载图片(返回 RGBA 数据) + /// + /// Load image (returns RGBA data). + fn load_image(&self, path: &str) -> impl Future> + Send; +} + +// ==================== 输入系统 | Input System ==================== + +/// 鼠标按钮 +/// +/// Mouse button. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MouseButton { + /// 左键 | Left button + Left, + /// 中键 | Middle button + Middle, + /// 右键 | Right button + Right, + /// 其他按钮 | Other button + Other(u8), +} + +/// 键盘键码(常用键) +/// +/// Keyboard key code (common keys). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum KeyCode { + // 字母键 + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + + // 数字键 + Key0, Key1, Key2, Key3, Key4, + Key5, Key6, Key7, Key8, Key9, + + // 功能键 + F1, F2, F3, F4, F5, F6, + F7, F8, F9, F10, F11, F12, + + // 控制键 + Escape, Tab, CapsLock, Shift, Control, Alt, + Space, Enter, Backspace, Delete, Insert, + Home, End, PageUp, PageDown, + + // 方向键 + Up, Down, Left, Right, + + // 符号键 + Minus, Equal, BracketLeft, BracketRight, + Backslash, Semicolon, Quote, Comma, Period, Slash, + Backquote, + + // 其他 + Unknown(u32), +} + +/// 输入状态 trait +/// +/// Input state trait. +pub trait InputState { + /// 检查按键是否按下 + /// + /// Check if key is pressed. + fn is_key_down(&self, key: KeyCode) -> bool; + + /// 检查按键是否刚按下(本帧) + /// + /// Check if key was just pressed (this frame). + fn is_key_just_pressed(&self, key: KeyCode) -> bool; + + /// 检查按键是否刚释放(本帧) + /// + /// Check if key was just released (this frame). + fn is_key_just_released(&self, key: KeyCode) -> bool; + + /// 检查鼠标按钮是否按下 + /// + /// Check if mouse button is pressed. + fn is_mouse_button_down(&self, button: MouseButton) -> bool; + + /// 获取鼠标位置 + /// + /// Get mouse position. + fn mouse_position(&self) -> (f32, f32); + + /// 获取鼠标滚轮增量 + /// + /// Get mouse wheel delta. + fn mouse_wheel_delta(&self) -> f32; + + /// 更新输入状态(每帧调用) + /// + /// Update input state (call every frame). + fn update(&mut self); +} + +// ==================== 平台 Trait | Platform Trait ==================== + +/// 平台抽象 trait +/// +/// 定义平台相关操作的抽象接口。 +/// +/// Platform abstraction trait. +/// Defines abstract interface for platform-specific operations. +pub trait Platform { + /// 后端类型 + /// + /// Backend type. + type Backend: GraphicsBackend; + + /// 资产加载器类型 + /// + /// Asset loader type. + type AssetLoader: AssetLoader; + + /// 输入状态类型 + /// + /// Input state type. + type Input: InputState; + + /// 创建图形后端 + /// + /// Create graphics backend. + fn create_backend(&self, config: BackendConfig) -> GraphicsResult; + + /// 获取资产加载器 + /// + /// Get asset loader. + fn asset_loader(&self) -> &Self::AssetLoader; + + /// 获取输入状态 + /// + /// Get input state. + fn input(&self) -> &Self::Input; + + /// 获取输入状态(可变) + /// + /// Get input state (mutable). + fn input_mut(&mut self) -> &mut Self::Input; + + /// 获取屏幕尺寸 + /// + /// Get screen size. + fn screen_size(&self) -> (u32, u32); + + /// 获取设备像素比 + /// + /// Get device pixel ratio. + fn device_pixel_ratio(&self) -> f32; + + /// 获取当前时间(秒) + /// + /// Get current time (seconds). + fn time(&self) -> f64; + + /// 请求下一帧 + /// + /// Request next frame. + fn request_animation_frame(&self, callback: impl FnOnce(f64) + 'static); +} + +// ==================== 平台类型枚举 | Platform Type Enum ==================== + +/// 平台类型 +/// +/// Platform type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PlatformType { + /// Web 浏览器 + /// + /// Web browser. + Web, + + /// 微信小游戏 + /// + /// WeChat Mini Game. + WeChatMiniGame, + + /// Windows 桌面 + /// + /// Windows desktop. + Windows, + + /// macOS 桌面 + /// + /// macOS desktop. + MacOS, + + /// Linux 桌面 + /// + /// Linux desktop. + Linux, + + /// Android + Android, + + /// iOS + IOS, + + /// 未知平台 + /// + /// Unknown platform. + Unknown, +} + +impl PlatformType { + /// 是否为桌面平台 + /// + /// Check if desktop platform. + pub const fn is_desktop(&self) -> bool { + matches!(self, Self::Windows | Self::MacOS | Self::Linux) + } + + /// 是否为移动平台 + /// + /// Check if mobile platform. + pub const fn is_mobile(&self) -> bool { + matches!(self, Self::Android | Self::IOS) + } + + /// 是否为 Web 平台 + /// + /// Check if web platform. + pub const fn is_web(&self) -> bool { + matches!(self, Self::Web | Self::WeChatMiniGame) + } + + /// 是否为原生平台 + /// + /// Check if native platform. + pub const fn is_native(&self) -> bool { + !self.is_web() + } +} diff --git a/packages/engine-shared/src/traits/renderer.rs b/packages/engine-shared/src/traits/renderer.rs new file mode 100644 index 00000000..f6593cce --- /dev/null +++ b/packages/engine-shared/src/traits/renderer.rs @@ -0,0 +1,293 @@ +//! 高级渲染器 trait +//! +//! High-level renderer trait. + +use super::backend::{GraphicsBackend, GraphicsResult}; +use crate::camera::Camera2D; + +// ==================== 精灵批处理数据 | Sprite Batch Data ==================== + +/// 精灵批处理数据 +/// +/// 与现有 TypeScript 层的 RenderBatcher 数据格式兼容。 +/// +/// Sprite batch data. +/// Compatible with existing TypeScript RenderBatcher data format. +#[derive(Debug)] +pub struct SpriteBatchData<'a> { + /// 变换数据:[x, y, rotation, scaleX, scaleY, originX, originY] per sprite + /// + /// Transform data: [x, y, rotation, scaleX, scaleY, originX, originY] per sprite. + pub transforms: &'a [f32], + + /// 纹理 ID(每个精灵一个) + /// + /// Texture ID (one per sprite). + pub texture_ids: &'a [u32], + + /// UV 坐标:[u0, v0, u1, v1] per sprite + /// + /// UV coordinates: [u0, v0, u1, v1] per sprite. + pub uvs: &'a [f32], + + /// 打包的 RGBA 颜色(每个精灵一个) + /// + /// Packed RGBA color (one per sprite). + pub colors: &'a [u32], + + /// 材质 ID(每个精灵一个,0 = 默认) + /// + /// Material ID (one per sprite, 0 = default). + pub material_ids: &'a [u32], +} + +impl<'a> SpriteBatchData<'a> { + /// 获取精灵数量 + /// + /// Get sprite count. + pub fn sprite_count(&self) -> usize { + self.texture_ids.len() + } + + /// 验证数据一致性 + /// + /// Validate data consistency. + pub fn validate(&self) -> Result<(), &'static str> { + let count = self.sprite_count(); + + if self.transforms.len() != count * 7 { + return Err("transforms length mismatch (expected count * 7)"); + } + if self.uvs.len() != count * 4 { + return Err("uvs length mismatch (expected count * 4)"); + } + if self.colors.len() != count { + return Err("colors length mismatch"); + } + if self.material_ids.len() != count { + return Err("material_ids length mismatch"); + } + + Ok(()) + } +} + +/// 相机状态 +/// +/// Camera state. +#[derive(Debug, Clone, Copy, Default)] +pub struct CameraState { + /// X 坐标 | X coordinate + pub x: f32, + /// Y 坐标 | Y coordinate + pub y: f32, + /// 缩放 | Zoom + pub zoom: f32, + /// 旋转(弧度)| Rotation (radians) + pub rotation: f32, +} + +impl CameraState { + /// 创建新的相机状态 + /// + /// Create new camera state. + pub const fn new(x: f32, y: f32, zoom: f32, rotation: f32) -> Self { + Self { x, y, zoom, rotation } + } + + /// 创建默认相机状态 + /// + /// Create default camera state. + pub const fn identity() -> Self { + Self { + x: 0.0, + y: 0.0, + zoom: 1.0, + rotation: 0.0, + } + } + + /// 转换为 Camera2D + /// + /// Convert to Camera2D. + pub fn to_camera2d(&self, width: f32, height: f32) -> Camera2D { + Camera2D::new(width, height) + .with_position(self.x, self.y) + .with_zoom(self.zoom) + .with_rotation(self.rotation) + } +} + +// ==================== 2D 渲染器 Trait | 2D Renderer Trait ==================== + +/// 2D 渲染器 trait +/// +/// 在 GraphicsBackend 基础上提供更高级的 2D 渲染 API。 +/// +/// 2D renderer trait. +/// Provides higher-level 2D rendering API built on top of GraphicsBackend. +pub trait Renderer2D { + /// 后端类型 + /// + /// Backend type. + type Backend: GraphicsBackend; + + /// 获取底层后端 + /// + /// Get underlying backend. + fn backend(&self) -> &Self::Backend; + + /// 获取底层后端(可变) + /// + /// Get underlying backend (mutable). + fn backend_mut(&mut self) -> &mut Self::Backend; + + /// 提交精灵批次 + /// + /// Submit sprite batch. + fn submit_sprite_batch(&mut self, data: SpriteBatchData) -> GraphicsResult<()>; + + /// 设置相机 + /// + /// Set camera. + fn set_camera(&mut self, state: CameraState); + + /// 获取相机状态 + /// + /// Get camera state. + fn camera(&self) -> CameraState; + + /// 渲染当前帧 + /// + /// Render current frame. + fn render(&mut self) -> GraphicsResult<()>; + + /// 清屏 + /// + /// Clear screen. + fn clear(&mut self, r: f32, g: f32, b: f32, a: f32) { + self.backend_mut().clear(r, g, b, a); + } + + /// 屏幕坐标转世界坐标 + /// + /// Screen to world coordinates. + fn screen_to_world(&self, screen_x: f32, screen_y: f32) -> (f32, f32); + + /// 世界坐标转屏幕坐标 + /// + /// World to screen coordinates. + fn world_to_screen(&self, world_x: f32, world_y: f32) -> (f32, f32); +} + +// ==================== Gizmo 渲染器 Trait | Gizmo Renderer Trait ==================== + +/// Gizmo 类型 +/// +/// Gizmo type. +#[derive(Debug, Clone)] +pub enum GizmoShape { + /// 矩形 + /// + /// Rectangle. + Rect { + x: f32, + y: f32, + width: f32, + height: f32, + rotation: f32, + origin_x: f32, + origin_y: f32, + show_handles: bool, + }, + + /// 圆形 + /// + /// Circle. + Circle { + x: f32, + y: f32, + radius: f32, + }, + + /// 线段/多边形 + /// + /// Line/polygon. + Line { + points: Vec<(f32, f32)>, + closed: bool, + }, + + /// 胶囊体 + /// + /// Capsule. + Capsule { + x: f32, + y: f32, + radius: f32, + half_height: f32, + rotation: f32, + }, +} + +/// Gizmo 渲染器 trait(编辑器功能) +/// +/// Gizmo renderer trait (editor feature). +pub trait GizmoRenderer { + /// 添加 Gizmo + /// + /// Add gizmo. + fn add_gizmo(&mut self, shape: GizmoShape, r: f32, g: f32, b: f32, a: f32); + + /// 清空 Gizmo + /// + /// Clear gizmos. + fn clear_gizmos(&mut self); + + /// 渲染 Gizmo + /// + /// Render gizmos. + fn render_gizmos(&mut self) -> GraphicsResult<()>; + + /// 是否显示 Gizmo + /// + /// Check if gizmos are visible. + fn gizmos_visible(&self) -> bool; + + /// 设置 Gizmo 可见性 + /// + /// Set gizmo visibility. + fn set_gizmos_visible(&mut self, visible: bool); +} + +// ==================== 网格渲染器 Trait | Grid Renderer Trait ==================== + +/// 网格渲染器 trait(编辑器功能) +/// +/// Grid renderer trait (editor feature). +pub trait GridRenderer { + /// 是否显示网格 + /// + /// Check if grid is visible. + fn grid_visible(&self) -> bool; + + /// 设置网格可见性 + /// + /// Set grid visibility. + fn set_grid_visible(&mut self, visible: bool); + + /// 渲染网格 + /// + /// Render grid. + fn render_grid(&mut self) -> GraphicsResult<()>; + + /// 设置网格大小 + /// + /// Set grid size. + fn set_grid_size(&mut self, size: f32); + + /// 设置网格颜色 + /// + /// Set grid color. + fn set_grid_color(&mut self, r: f32, g: f32, b: f32, a: f32); +} diff --git a/packages/engine-shared/src/types/blend.rs b/packages/engine-shared/src/types/blend.rs new file mode 100644 index 00000000..c88968e4 --- /dev/null +++ b/packages/engine-shared/src/types/blend.rs @@ -0,0 +1,333 @@ +//! 混合模式与渲染状态 +//! +//! Blend modes and render state. + +/// 混合模式 +/// +/// Blend mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum BlendMode { + /// 无混合(禁用 alpha 混合) + /// + /// No blending (disable alpha blending). + None, + + /// Alpha 混合(默认) + /// + /// srcRGB * srcAlpha + dstRGB * (1 - srcAlpha) + /// + /// Alpha blending (default). + #[default] + Alpha, + + /// 加法混合(发光效果) + /// + /// srcRGB + dstRGB + /// + /// Additive blending (glow effects). + Additive, + + /// 乘法混合(阴影效果) + /// + /// srcRGB * dstRGB + /// + /// Multiply blending (shadow effects). + Multiply, + + /// 屏幕混合(提亮效果) + /// + /// 1 - (1 - srcRGB) * (1 - dstRGB) + /// + /// Screen blending (lighten effects). + Screen, + + /// 预乘 Alpha + /// + /// srcRGB + dstRGB * (1 - srcAlpha) + /// + /// Premultiplied alpha. + PremultipliedAlpha, +} + +impl BlendMode { + /// 获取所有混合模式 + /// + /// Get all blend modes. + pub const fn all() -> &'static [BlendMode] { + &[ + BlendMode::None, + BlendMode::Alpha, + BlendMode::Additive, + BlendMode::Multiply, + BlendMode::Screen, + BlendMode::PremultipliedAlpha, + ] + } + + /// 获取混合模式名称 + /// + /// Get blend mode name. + pub const fn name(&self) -> &'static str { + match self { + Self::None => "None", + Self::Alpha => "Alpha", + Self::Additive => "Additive", + Self::Multiply => "Multiply", + Self::Screen => "Screen", + Self::PremultipliedAlpha => "PremultipliedAlpha", + } + } +} + +/// 裁剪模式(背面剔除) +/// +/// Cull mode (backface culling). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CullMode { + /// 无裁剪(双面渲染) + /// + /// No culling (double-sided rendering). + #[default] + None, + + /// 裁剪正面 + /// + /// Cull front faces. + Front, + + /// 裁剪背面 + /// + /// Cull back faces. + Back, +} + +/// 比较函数(深度/模板测试) +/// +/// Comparison function (depth/stencil test). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CompareFunc { + /// 永不通过 | Never pass + Never, + /// 小于时通过 | Pass if less + Less, + /// 等于时通过 | Pass if equal + Equal, + /// 小于等于时通过 | Pass if less or equal + LessEqual, + /// 大于时通过 | Pass if greater + Greater, + /// 不等于时通过 | Pass if not equal + NotEqual, + /// 大于等于时通过 | Pass if greater or equal + GreaterEqual, + /// 总是通过(默认) | Always pass (default) + #[default] + Always, +} + +/// 裁剪矩形 +/// +/// Scissor rectangle. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScissorRect { + /// X 坐标 | X coordinate + pub x: i32, + /// Y 坐标 | Y coordinate + pub y: i32, + /// 宽度 | Width + pub width: u32, + /// 高度 | Height + pub height: u32, +} + +impl ScissorRect { + /// 创建新的裁剪矩形 + /// + /// Create new scissor rectangle. + pub const fn new(x: i32, y: i32, width: u32, height: u32) -> Self { + Self { x, y, width, height } + } + + /// 从位置和尺寸创建 + /// + /// Create from position and size. + pub const fn from_pos_size(x: i32, y: i32, width: u32, height: u32) -> Self { + Self::new(x, y, width, height) + } +} + +/// 视口 +/// +/// Viewport. +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Viewport { + /// X 坐标 | X coordinate + pub x: f32, + /// Y 坐标 | Y coordinate + pub y: f32, + /// 宽度 | Width + pub width: f32, + /// 高度 | Height + pub height: f32, + /// 最小深度 | Min depth + pub min_depth: f32, + /// 最大深度 | Max depth + pub max_depth: f32, +} + +impl Default for Viewport { + fn default() -> Self { + Self { + x: 0.0, + y: 0.0, + width: 1.0, + height: 1.0, + min_depth: 0.0, + max_depth: 1.0, + } + } +} + +impl Viewport { + /// 创建新的视口 + /// + /// Create new viewport. + pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self { + Self { + x, + y, + width, + height, + min_depth: 0.0, + max_depth: 1.0, + } + } +} + +/// 渲染状态描述 +/// +/// Render state descriptor. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RenderState { + /// 混合模式 | Blend mode + pub blend_mode: BlendMode, + + /// 裁剪模式 | Cull mode + pub cull_mode: CullMode, + + /// 是否启用深度测试 | Enable depth test + pub depth_test: bool, + + /// 是否启用深度写入 | Enable depth write + pub depth_write: bool, + + /// 深度比较函数 | Depth comparison function + pub depth_func: CompareFunc, + + /// 裁剪矩形(None 表示禁用) | Scissor rect (None to disable) + pub scissor: Option, +} + +impl RenderState { + /// 创建默认 2D 渲染状态 + /// + /// Create default 2D render state. + pub fn default_2d() -> Self { + Self { + blend_mode: BlendMode::Alpha, + cull_mode: CullMode::None, + depth_test: false, + depth_write: false, + depth_func: CompareFunc::Always, + scissor: None, + } + } + + /// 创建不透明 2D 渲染状态 + /// + /// Create opaque 2D render state. + pub fn opaque_2d() -> Self { + Self { + blend_mode: BlendMode::None, + cull_mode: CullMode::None, + depth_test: false, + depth_write: false, + depth_func: CompareFunc::Always, + scissor: None, + } + } + + /// 创建加法混合状态 + /// + /// Create additive blend state. + pub fn additive() -> Self { + Self { + blend_mode: BlendMode::Additive, + ..Self::default_2d() + } + } +} + +/// 清除标志 +/// +/// Clear flags. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ClearFlags { + bits: u8, +} + +impl ClearFlags { + /// 无清除 | No clear + pub const NONE: Self = Self { bits: 0 }; + /// 清除颜色缓冲 | Clear color buffer + pub const COLOR: Self = Self { bits: 1 }; + /// 清除深度缓冲 | Clear depth buffer + pub const DEPTH: Self = Self { bits: 2 }; + /// 清除模板缓冲 | Clear stencil buffer + pub const STENCIL: Self = Self { bits: 4 }; + /// 清除所有缓冲 | Clear all buffers + pub const ALL: Self = Self { bits: 7 }; + + /// 是否包含颜色清除 | Contains color clear + pub const fn has_color(&self) -> bool { + self.bits & Self::COLOR.bits != 0 + } + + /// 是否包含深度清除 | Contains depth clear + pub const fn has_depth(&self) -> bool { + self.bits & Self::DEPTH.bits != 0 + } + + /// 是否包含模板清除 | Contains stencil clear + pub const fn has_stencil(&self) -> bool { + self.bits & Self::STENCIL.bits != 0 + } +} + +impl std::ops::BitOr for ClearFlags { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self { bits: self.bits | rhs.bits } + } +} + +impl std::ops::BitAnd for ClearFlags { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self { bits: self.bits & rhs.bits } + } +} + +impl Default for ClearFlags { + fn default() -> Self { + Self::COLOR + } +} diff --git a/packages/engine-shared/src/types/handle.rs b/packages/engine-shared/src/types/handle.rs new file mode 100644 index 00000000..50e09191 --- /dev/null +++ b/packages/engine-shared/src/types/handle.rs @@ -0,0 +1,400 @@ +//! 类型安全的资源句柄 +//! +//! Type-safe resource handles. + +use std::marker::PhantomData; +use std::fmt; +use std::hash::{Hash, Hasher}; + +/// 类型安全的资源句柄 +/// +/// 使用 PhantomData 区分不同资源类型,防止句柄混用。 +/// 包含代数(generation)用于检测过期句柄。 +/// +/// Type-safe resource handle using PhantomData to distinguish resource types. +/// Includes generation for stale handle detection. +/// +/// # Example +/// +/// ``` +/// use es_engine_shared::types::handle::*; +/// +/// let texture: TextureHandle = TextureHandle::new(1, 1); +/// let buffer: BufferHandle = BufferHandle::new(1, 1); +/// +/// // 编译错误:类型不匹配 +/// // Compile error: type mismatch +/// // let _: TextureHandle = buffer; +/// ``` +#[repr(C)] +pub struct Handle { + /// 内部 ID | Internal ID + id: u32, + /// 代数(用于检测过期句柄) | Generation for stale handle detection + generation: u32, + /// 类型标记 | Type marker + _marker: PhantomData, +} + +// 手动实现 trait,因为 PhantomData 的存在 +impl Clone for Handle { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Handle {} + +impl PartialEq for Handle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.generation == other.generation + } +} + +impl Eq for Handle {} + +impl Hash for Handle { + fn hash(&self, state: &mut H) { + self.id.hash(state); + self.generation.hash(state); + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Handle") + .field("id", &self.id) + .field("generation", &self.generation) + .finish() + } +} + +impl Default for Handle { + fn default() -> Self { + Self::null() + } +} + +impl Handle { + /// 创建新句柄 + /// + /// Create new handle. + #[inline] + pub const fn new(id: u32, generation: u32) -> Self { + Self { + id, + generation, + _marker: PhantomData, + } + } + + /// 获取原始 ID + /// + /// Get raw ID. + #[inline] + pub const fn id(&self) -> u32 { + self.id + } + + /// 获取代数 + /// + /// Get generation. + #[inline] + pub const fn generation(&self) -> u32 { + self.generation + } + + /// 创建空句柄 + /// + /// Create null handle. + #[inline] + pub const fn null() -> Self { + Self::new(u32::MAX, 0) + } + + /// 是否为空句柄 + /// + /// Check if null handle. + #[inline] + pub const fn is_null(&self) -> bool { + self.id == u32::MAX + } + + /// 是否为有效句柄(非空) + /// + /// Check if valid handle (not null). + #[inline] + pub const fn is_valid(&self) -> bool { + self.id != u32::MAX + } + + /// 转换为原始值(用于序列化等) + /// + /// Convert to raw value (for serialization, etc.). + #[inline] + pub const fn to_raw(&self) -> (u32, u32) { + (self.id, self.generation) + } + + /// 从原始值创建(用于反序列化等) + /// + /// Create from raw value (for deserialization, etc.). + #[inline] + pub const fn from_raw(id: u32, generation: u32) -> Self { + Self::new(id, generation) + } +} + +// ==================== 资源类型标记 | Resource Type Markers ==================== + +/// 缓冲区类型标记 | Buffer type marker +#[derive(Debug, Clone, Copy)] +pub struct BufferMarker; + +/// 顶点数组对象类型标记 | Vertex array object type marker +#[derive(Debug, Clone, Copy)] +pub struct VertexArrayMarker; + +/// 着色器程序类型标记 | Shader program type marker +#[derive(Debug, Clone, Copy)] +pub struct ShaderMarker; + +/// 纹理类型标记 | Texture type marker +#[derive(Debug, Clone, Copy)] +pub struct TextureMarker; + +/// 材质类型标记 | Material type marker +#[derive(Debug, Clone, Copy)] +pub struct MaterialMarker; + +/// 帧缓冲区类型标记 | Framebuffer type marker +#[derive(Debug, Clone, Copy)] +pub struct FramebufferMarker; + +/// 渲染缓冲区类型标记 | Renderbuffer type marker +#[derive(Debug, Clone, Copy)] +pub struct RenderbufferMarker; + +/// 采样器类型标记 | Sampler type marker +#[derive(Debug, Clone, Copy)] +pub struct SamplerMarker; + +// ==================== 类型别名 | Type Aliases ==================== + +/// 缓冲区句柄 | Buffer handle +pub type BufferHandle = Handle; + +/// 顶点数组对象句柄 | Vertex array object handle +pub type VertexArrayHandle = Handle; + +/// 着色器程序句柄 | Shader program handle +pub type ShaderHandle = Handle; + +/// 纹理句柄 | Texture handle +pub type TextureHandle = Handle; + +/// 材质句柄 | Material handle +pub type MaterialHandle = Handle; + +/// 帧缓冲区句柄 | Framebuffer handle +pub type FramebufferHandle = Handle; + +/// 渲染缓冲区句柄 | Renderbuffer handle +pub type RenderbufferHandle = Handle; + +/// 采样器句柄 | Sampler handle +pub type SamplerHandle = Handle; + +// ==================== 句柄映射 | Handle Map ==================== + +/// 带代数的句柄映射 +/// +/// 用于管理资源的分配和释放,支持句柄重用和过期检测。 +/// +/// Handle map with generation tracking. +/// Used for managing resource allocation and deallocation, +/// supports handle reuse and stale detection. +pub struct HandleMap { + /// 存储项:(资源, 代数) | Items: (resource, generation) + items: Vec>, + /// 空闲列表 | Free list + free_list: Vec, + /// 下一个代数 | Next generation + next_generation: u32, +} + +impl Default for HandleMap { + fn default() -> Self { + Self::new() + } +} + +impl HandleMap { + /// 创建新的句柄映射 + /// + /// Create new handle map. + pub fn new() -> Self { + Self { + items: Vec::new(), + free_list: Vec::new(), + next_generation: 1, + } + } + + /// 创建带预分配容量的句柄映射 + /// + /// Create handle map with pre-allocated capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + items: Vec::with_capacity(capacity), + free_list: Vec::new(), + next_generation: 1, + } + } + + /// 插入资源并返回句柄 + /// + /// Insert resource and return handle. + pub fn insert(&mut self, item: T) -> Handle { + let generation = self.next_generation; + self.next_generation = self.next_generation.wrapping_add(1); + if self.next_generation == 0 { + self.next_generation = 1; // 跳过 0,避免与空句柄混淆 + } + + let id = if let Some(id) = self.free_list.pop() { + self.items[id as usize] = Some((item, generation)); + id + } else { + let id = self.items.len() as u32; + self.items.push(Some((item, generation))); + id + }; + + Handle::new(id, generation) + } + + /// 获取资源引用 + /// + /// Get resource reference. + pub fn get(&self, handle: Handle) -> Option<&T> { + self.items + .get(handle.id() as usize)? + .as_ref() + .filter(|(_, gen)| *gen == handle.generation()) + .map(|(item, _)| item) + } + + /// 获取资源可变引用 + /// + /// Get mutable resource reference. + pub fn get_mut(&mut self, handle: Handle) -> Option<&mut T> { + self.items + .get_mut(handle.id() as usize)? + .as_mut() + .filter(|(_, gen)| *gen == handle.generation()) + .map(|(item, _)| item) + } + + /// 移除资源 + /// + /// Remove resource. + pub fn remove(&mut self, handle: Handle) -> Option { + let slot = self.items.get_mut(handle.id() as usize)?; + if let Some((_, gen)) = slot { + if *gen == handle.generation() { + let item = slot.take().map(|(item, _)| item); + self.free_list.push(handle.id()); + return item; + } + } + None + } + + /// 检查句柄是否有效 + /// + /// Check if handle is valid. + pub fn contains(&self, handle: Handle) -> bool { + self.get(handle).is_some() + } + + /// 获取当前资源数量 + /// + /// Get current resource count. + pub fn len(&self) -> usize { + self.items.len() - self.free_list.len() + } + + /// 是否为空 + /// + /// Check if empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// 清空所有资源 + /// + /// Clear all resources. + pub fn clear(&mut self) { + self.items.clear(); + self.free_list.clear(); + } + + /// 迭代所有有效资源 + /// + /// Iterate over all valid resources. + pub fn iter(&self) -> impl Iterator { + self.items.iter().filter_map(|opt| opt.as_ref().map(|(item, _)| item)) + } + + /// 迭代所有有效资源(可变) + /// + /// Iterate over all valid resources (mutable). + pub fn iter_mut(&mut self) -> impl Iterator { + self.items.iter_mut().filter_map(|opt| opt.as_mut().map(|(item, _)| item)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_handle_creation() { + let handle: TextureHandle = TextureHandle::new(1, 1); + assert_eq!(handle.id(), 1); + assert_eq!(handle.generation(), 1); + assert!(handle.is_valid()); + assert!(!handle.is_null()); + } + + #[test] + fn test_null_handle() { + let handle: BufferHandle = BufferHandle::null(); + assert!(handle.is_null()); + assert!(!handle.is_valid()); + } + + #[test] + fn test_handle_map() { + let mut map: HandleMap = HandleMap::new(); + + let h1: TextureHandle = map.insert("texture1".to_string()); + let h2: TextureHandle = map.insert("texture2".to_string()); + + assert_eq!(map.get(h1), Some(&"texture1".to_string())); + assert_eq!(map.get(h2), Some(&"texture2".to_string())); + assert_eq!(map.len(), 2); + + // 移除后句柄失效 + let removed = map.remove(h1); + assert_eq!(removed, Some("texture1".to_string())); + assert_eq!(map.get(h1), None); + assert_eq!(map.len(), 1); + + // 重用空闲槽位 + let h3: TextureHandle = map.insert("texture3".to_string()); + assert_eq!(h3.id(), h1.id()); // 重用了 h1 的 ID + assert_ne!(h3.generation(), h1.generation()); // 但代数不同 + } +} diff --git a/packages/engine-shared/src/types/mod.rs b/packages/engine-shared/src/types/mod.rs new file mode 100644 index 00000000..38df8b94 --- /dev/null +++ b/packages/engine-shared/src/types/mod.rs @@ -0,0 +1,9 @@ +//! 共享类型定义 +//! +//! Shared type definitions. + +pub mod handle; +pub mod vertex; +pub mod blend; +pub mod uniform; +pub mod texture; diff --git a/packages/engine-shared/src/types/texture.rs b/packages/engine-shared/src/types/texture.rs new file mode 100644 index 00000000..7b6263dc --- /dev/null +++ b/packages/engine-shared/src/types/texture.rs @@ -0,0 +1,383 @@ +//! 纹理相关类型定义 +//! +//! Texture-related type definitions. + +/// 纹理格式 +/// +/// Texture format. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TextureFormat { + /// RGBA 8-bit(默认)| RGBA 8-bit (default) + #[default] + RGBA8, + /// RGB 8-bit | RGB 8-bit + RGB8, + /// 单通道 8-bit | Single channel 8-bit + R8, + /// 双通道 8-bit | Dual channel 8-bit + RG8, + /// RGBA 16-bit 浮点 | RGBA 16-bit float + RGBA16F, + /// RGBA 32-bit 浮点 | RGBA 32-bit float + RGBA32F, + /// 24-bit 深度 | 24-bit depth + Depth24, + /// 32-bit 浮点深度 | 32-bit float depth + Depth32F, + /// 24-bit 深度 + 8-bit 模板 | 24-bit depth + 8-bit stencil + Depth24Stencil8, +} + +impl TextureFormat { + /// 获取每像素字节数 + /// + /// Get bytes per pixel. + pub const fn bytes_per_pixel(&self) -> usize { + match self { + Self::R8 => 1, + Self::RG8 => 2, + Self::RGB8 => 3, + Self::RGBA8 => 4, + Self::RGBA16F => 8, + Self::RGBA32F => 16, + Self::Depth24 => 3, + Self::Depth32F => 4, + Self::Depth24Stencil8 => 4, + } + } + + /// 是否为深度格式 + /// + /// Check if depth format. + pub const fn is_depth(&self) -> bool { + matches!(self, Self::Depth24 | Self::Depth32F | Self::Depth24Stencil8) + } + + /// 是否为浮点格式 + /// + /// Check if float format. + pub const fn is_float(&self) -> bool { + matches!(self, Self::RGBA16F | Self::RGBA32F | Self::Depth32F) + } + + /// 获取通道数 + /// + /// Get channel count. + pub const fn channel_count(&self) -> u32 { + match self { + Self::R8 | Self::Depth24 | Self::Depth32F => 1, + Self::RG8 | Self::Depth24Stencil8 => 2, + Self::RGB8 => 3, + Self::RGBA8 | Self::RGBA16F | Self::RGBA32F => 4, + } + } +} + +/// 纹理过滤模式 +/// +/// Texture filter mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TextureFilter { + /// 最近邻(像素风格,默认)| Nearest neighbor (pixel art, default) + #[default] + Nearest, + /// 线性插值(平滑)| Linear interpolation (smooth) + Linear, + /// 最近邻 + 最近邻 mipmap | Nearest + nearest mipmap + NearestMipmapNearest, + /// 线性 + 最近邻 mipmap | Linear + nearest mipmap + LinearMipmapNearest, + /// 最近邻 + 线性 mipmap | Nearest + linear mipmap + NearestMipmapLinear, + /// 线性 + 线性 mipmap(三线性过滤)| Linear + linear mipmap (trilinear) + LinearMipmapLinear, +} + +impl TextureFilter { + /// 是否需要 mipmap + /// + /// Check if mipmap required. + pub const fn requires_mipmap(&self) -> bool { + matches!( + self, + Self::NearestMipmapNearest + | Self::LinearMipmapNearest + | Self::NearestMipmapLinear + | Self::LinearMipmapLinear + ) + } +} + +/// 纹理环绕模式 +/// +/// Texture wrap mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TextureWrap { + /// 钳制到边缘(默认)| Clamp to edge (default) + #[default] + ClampToEdge, + /// 重复 | Repeat + Repeat, + /// 镜像重复 | Mirrored repeat + MirroredRepeat, +} + +/// 纹理描述符 +/// +/// Texture descriptor. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TextureDescriptor { + /// 宽度 | Width + pub width: u32, + /// 高度 | Height + pub height: u32, + /// 格式 | Format + pub format: TextureFormat, + /// 缩小过滤 | Minification filter + pub filter_min: TextureFilter, + /// 放大过滤 | Magnification filter + pub filter_mag: TextureFilter, + /// S 方向(水平)环绕 | S (horizontal) wrap + pub wrap_s: TextureWrap, + /// T 方向(垂直)环绕 | T (vertical) wrap + pub wrap_t: TextureWrap, + /// 是否生成 mipmap | Generate mipmaps + pub generate_mipmaps: bool, + /// 标签(调试用)| Label (for debugging) + pub label: Option, +} + +impl Default for TextureDescriptor { + fn default() -> Self { + Self { + width: 1, + height: 1, + format: TextureFormat::RGBA8, + filter_min: TextureFilter::Nearest, + filter_mag: TextureFilter::Nearest, + wrap_s: TextureWrap::ClampToEdge, + wrap_t: TextureWrap::ClampToEdge, + generate_mipmaps: false, + label: None, + } + } +} + +impl TextureDescriptor { + /// 创建新的纹理描述符 + /// + /// Create new texture descriptor. + pub fn new(width: u32, height: u32) -> Self { + Self { + width, + height, + ..Default::default() + } + } + + /// 设置格式 + /// + /// Set format. + pub fn with_format(mut self, format: TextureFormat) -> Self { + self.format = format; + self + } + + /// 设置过滤模式(同时设置缩小和放大) + /// + /// Set filter mode (both min and mag). + pub fn with_filter(mut self, filter: TextureFilter) -> Self { + self.filter_min = filter; + self.filter_mag = filter; + self + } + + /// 设置环绕模式(同时设置 S 和 T) + /// + /// Set wrap mode (both S and T). + pub fn with_wrap(mut self, wrap: TextureWrap) -> Self { + self.wrap_s = wrap; + self.wrap_t = wrap; + self + } + + /// 启用 mipmap 生成 + /// + /// Enable mipmap generation. + pub fn with_mipmaps(mut self) -> Self { + self.generate_mipmaps = true; + self + } + + /// 设置标签 + /// + /// Set label. + pub fn with_label(mut self, label: impl Into) -> Self { + self.label = Some(label.into()); + self + } + + /// 创建像素风格纹理描述符 + /// + /// Create pixel art texture descriptor. + pub fn pixel_art(width: u32, height: u32) -> Self { + Self::new(width, height) + .with_filter(TextureFilter::Nearest) + .with_wrap(TextureWrap::ClampToEdge) + } + + /// 创建平滑纹理描述符 + /// + /// Create smooth texture descriptor. + pub fn smooth(width: u32, height: u32) -> Self { + Self::new(width, height) + .with_filter(TextureFilter::Linear) + .with_wrap(TextureWrap::ClampToEdge) + } + + /// 创建平铺纹理描述符 + /// + /// Create tiled texture descriptor. + pub fn tiled(width: u32, height: u32) -> Self { + Self::new(width, height) + .with_filter(TextureFilter::Nearest) + .with_wrap(TextureWrap::Repeat) + } + + /// 计算纹理数据大小(字节) + /// + /// Calculate texture data size (bytes). + pub fn data_size(&self) -> usize { + self.width as usize * self.height as usize * self.format.bytes_per_pixel() + } +} + +/// 纹理状态 +/// +/// Texture state. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TextureState { + /// 加载中 | Loading + Loading, + /// 就绪 | Ready + Ready, + /// 加载失败 | Failed + Failed(String), +} + +impl TextureState { + /// 是否就绪 + /// + /// Check if ready. + pub const fn is_ready(&self) -> bool { + matches!(self, Self::Ready) + } + + /// 是否加载中 + /// + /// Check if loading. + pub const fn is_loading(&self) -> bool { + matches!(self, Self::Loading) + } + + /// 是否失败 + /// + /// Check if failed. + pub const fn is_failed(&self) -> bool { + matches!(self, Self::Failed(_)) + } +} + +/// 图片数据(用于纹理上传) +/// +/// Image data (for texture upload). +#[derive(Debug, Clone)] +pub struct ImageData { + /// 宽度 | Width + pub width: u32, + /// 高度 | Height + pub height: u32, + /// 像素数据(RGBA8 格式)| Pixel data (RGBA8 format) + pub data: Vec, +} + +impl ImageData { + /// 创建新的图片数据 + /// + /// Create new image data. + pub fn new(width: u32, height: u32, data: Vec) -> Self { + debug_assert_eq!( + data.len(), + (width * height * 4) as usize, + "Data size mismatch" + ); + Self { width, height, data } + } + + /// 创建空白图片 + /// + /// Create blank image. + pub fn blank(width: u32, height: u32) -> Self { + Self { + width, + height, + data: vec![0; (width * height * 4) as usize], + } + } + + /// 创建纯色图片 + /// + /// Create solid color image. + pub fn solid(width: u32, height: u32, r: u8, g: u8, b: u8, a: u8) -> Self { + let pixel_count = (width * height) as usize; + let mut data = Vec::with_capacity(pixel_count * 4); + for _ in 0..pixel_count { + data.extend_from_slice(&[r, g, b, a]); + } + Self { width, height, data } + } + + /// 创建白色 1x1 纹理(默认纹理) + /// + /// Create white 1x1 texture (default texture). + pub fn white_pixel() -> Self { + Self::solid(1, 1, 255, 255, 255, 255) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_texture_descriptor_builder() { + let desc = TextureDescriptor::new(256, 256) + .with_format(TextureFormat::RGBA8) + .with_filter(TextureFilter::Linear) + .with_wrap(TextureWrap::Repeat) + .with_label("test_texture"); + + assert_eq!(desc.width, 256); + assert_eq!(desc.height, 256); + assert_eq!(desc.filter_min, TextureFilter::Linear); + assert_eq!(desc.wrap_s, TextureWrap::Repeat); + assert_eq!(desc.label, Some("test_texture".to_string())); + } + + #[test] + fn test_image_data_size() { + let img = ImageData::blank(64, 64); + assert_eq!(img.data.len(), 64 * 64 * 4); + } + + #[test] + fn test_texture_format_bytes() { + assert_eq!(TextureFormat::R8.bytes_per_pixel(), 1); + assert_eq!(TextureFormat::RGBA8.bytes_per_pixel(), 4); + assert_eq!(TextureFormat::RGBA16F.bytes_per_pixel(), 8); + } +} diff --git a/packages/engine-shared/src/types/uniform.rs b/packages/engine-shared/src/types/uniform.rs new file mode 100644 index 00000000..caec0283 --- /dev/null +++ b/packages/engine-shared/src/types/uniform.rs @@ -0,0 +1,307 @@ +//! Uniform 类型定义 +//! +//! Uniform type definitions. + +use glam::{Vec2, Vec3, Vec4, Mat3, Mat4}; + +/// Uniform 值类型 +/// +/// Uniform value type. +#[derive(Debug, Clone)] +pub enum UniformValue { + /// 单精度浮点 | Single float + Float(f32), + /// 二维向量 | 2D vector + Float2(Vec2), + /// 三维向量 | 3D vector + Float3(Vec3), + /// 四维向量 | 4D vector + Float4(Vec4), + /// 有符号整数 | Signed integer + Int(i32), + /// 二维整数向量 | 2D integer vector + Int2([i32; 2]), + /// 三维整数向量 | 3D integer vector + Int3([i32; 3]), + /// 四维整数向量 | 4D integer vector + Int4([i32; 4]), + /// 无符号整数 | Unsigned integer + UInt(u32), + /// 3x3 矩阵 | 3x3 matrix + Mat3(Mat3), + /// 4x4 矩阵 | 4x4 matrix + Mat4(Mat4), + /// 纹理单元索引 | Texture unit index + Texture(u32), +} + +impl UniformValue { + /// 获取类型名称 + /// + /// Get type name. + pub const fn type_name(&self) -> &'static str { + match self { + Self::Float(_) => "float", + Self::Float2(_) => "vec2", + Self::Float3(_) => "vec3", + Self::Float4(_) => "vec4", + Self::Int(_) => "int", + Self::Int2(_) => "ivec2", + Self::Int3(_) => "ivec3", + Self::Int4(_) => "ivec4", + Self::UInt(_) => "uint", + Self::Mat3(_) => "mat3", + Self::Mat4(_) => "mat4", + Self::Texture(_) => "sampler2D", + } + } + + /// 创建颜色 uniform(vec4) + /// + /// Create color uniform (vec4). + pub fn color(r: f32, g: f32, b: f32, a: f32) -> Self { + Self::Float4(Vec4::new(r, g, b, a)) + } + + /// 创建颜色 uniform(从 u32 RGBA) + /// + /// Create color uniform (from u32 RGBA). + pub fn color_from_u32(rgba: u32) -> Self { + let r = ((rgba >> 24) & 0xFF) as f32 / 255.0; + let g = ((rgba >> 16) & 0xFF) as f32 / 255.0; + let b = ((rgba >> 8) & 0xFF) as f32 / 255.0; + let a = (rgba & 0xFF) as f32 / 255.0; + Self::color(r, g, b, a) + } +} + +// ==================== From 实现 | From Implementations ==================== + +impl From for UniformValue { + fn from(v: f32) -> Self { + Self::Float(v) + } +} + +impl From for UniformValue { + fn from(v: Vec2) -> Self { + Self::Float2(v) + } +} + +impl From for UniformValue { + fn from(v: Vec3) -> Self { + Self::Float3(v) + } +} + +impl From for UniformValue { + fn from(v: Vec4) -> Self { + Self::Float4(v) + } +} + +impl From for UniformValue { + fn from(v: i32) -> Self { + Self::Int(v) + } +} + +impl From<[i32; 2]> for UniformValue { + fn from(v: [i32; 2]) -> Self { + Self::Int2(v) + } +} + +impl From<[i32; 3]> for UniformValue { + fn from(v: [i32; 3]) -> Self { + Self::Int3(v) + } +} + +impl From<[i32; 4]> for UniformValue { + fn from(v: [i32; 4]) -> Self { + Self::Int4(v) + } +} + +impl From for UniformValue { + fn from(v: u32) -> Self { + Self::UInt(v) + } +} + +impl From for UniformValue { + fn from(v: Mat3) -> Self { + Self::Mat3(v) + } +} + +impl From for UniformValue { + fn from(v: Mat4) -> Self { + Self::Mat4(v) + } +} + +/// Uniform 绑定描述 +/// +/// Uniform binding descriptor. +#[derive(Debug, Clone)] +pub struct UniformBinding { + /// Uniform 名称 | Uniform name + pub name: String, + /// Uniform 值 | Uniform value + pub value: UniformValue, +} + +impl UniformBinding { + /// 创建新的 uniform 绑定 + /// + /// Create new uniform binding. + pub fn new(name: impl Into, value: impl Into) -> Self { + Self { + name: name.into(), + value: value.into(), + } + } +} + +/// Uniform 集合 +/// +/// Uniform set. +#[derive(Debug, Clone, Default)] +pub struct UniformSet { + /// Uniform 列表 | Uniform list + bindings: Vec, +} + +impl UniformSet { + /// 创建新的 uniform 集合 + /// + /// Create new uniform set. + pub fn new() -> Self { + Self::default() + } + + /// 创建带容量的 uniform 集合 + /// + /// Create uniform set with capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + bindings: Vec::with_capacity(capacity), + } + } + + /// 设置 uniform 值 + /// + /// Set uniform value. + pub fn set(&mut self, name: impl Into, value: impl Into) { + let name = name.into(); + let value = value.into(); + + // 查找已存在的 uniform + for binding in &mut self.bindings { + if binding.name == name { + binding.value = value; + return; + } + } + + // 添加新的 uniform + self.bindings.push(UniformBinding { name, value }); + } + + /// 获取 uniform 值 + /// + /// Get uniform value. + pub fn get(&self, name: &str) -> Option<&UniformValue> { + self.bindings + .iter() + .find(|b| b.name == name) + .map(|b| &b.value) + } + + /// 移除 uniform + /// + /// Remove uniform. + pub fn remove(&mut self, name: &str) -> Option { + if let Some(idx) = self.bindings.iter().position(|b| b.name == name) { + Some(self.bindings.remove(idx).value) + } else { + None + } + } + + /// 清空所有 uniform + /// + /// Clear all uniforms. + pub fn clear(&mut self) { + self.bindings.clear(); + } + + /// 迭代所有 uniform + /// + /// Iterate over all uniforms. + pub fn iter(&self) -> impl Iterator { + self.bindings.iter() + } + + /// 获取 uniform 数量 + /// + /// Get uniform count. + pub fn len(&self) -> usize { + self.bindings.len() + } + + /// 是否为空 + /// + /// Check if empty. + pub fn is_empty(&self) -> bool { + self.bindings.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_uniform_set() { + let mut set = UniformSet::new(); + + set.set("u_time", 1.5f32); + set.set("u_resolution", Vec2::new(800.0, 600.0)); + set.set("u_color", Vec4::new(1.0, 0.0, 0.0, 1.0)); + + assert_eq!(set.len(), 3); + + if let Some(UniformValue::Float(v)) = set.get("u_time") { + assert_eq!(*v, 1.5); + } else { + panic!("Expected Float"); + } + + // 更新已存在的 uniform + set.set("u_time", 2.0f32); + assert_eq!(set.len(), 3); // 数量不变 + + if let Some(UniformValue::Float(v)) = set.get("u_time") { + assert_eq!(*v, 2.0); + } else { + panic!("Expected Float"); + } + } + + #[test] + fn test_color_from_u32() { + let color = UniformValue::color_from_u32(0xFF0000FF); // Red + if let UniformValue::Float4(v) = color { + assert!((v.x - 1.0).abs() < 0.001); + assert!((v.y - 0.0).abs() < 0.001); + assert!((v.z - 0.0).abs() < 0.001); + assert!((v.w - 1.0).abs() < 0.001); + } else { + panic!("Expected Float4"); + } + } +} diff --git a/packages/engine-shared/src/types/vertex.rs b/packages/engine-shared/src/types/vertex.rs new file mode 100644 index 00000000..bc888f88 --- /dev/null +++ b/packages/engine-shared/src/types/vertex.rs @@ -0,0 +1,352 @@ +//! 顶点格式定义 +//! +//! Vertex format definitions. + +use bytemuck::{Pod, Zeroable}; + +/// 顶点属性类型 +/// +/// Vertex attribute type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum VertexAttributeType { + /// 单精度浮点 | Single float + Float, + /// 二维向量 | 2D vector + Float2, + /// 三维向量 | 3D vector + Float3, + /// 四维向量 | 4D vector + Float4, + /// 有符号整数 | Signed integer + Int, + /// 二维整数向量 | 2D integer vector + Int2, + /// 三维整数向量 | 3D integer vector + Int3, + /// 四维整数向量 | 4D integer vector + Int4, + /// 无符号整数 | Unsigned integer + UInt, + /// 二维无符号整数向量 | 2D unsigned integer vector + UInt2, + /// 三维无符号整数向量 | 3D unsigned integer vector + UInt3, + /// 四维无符号整数向量 | 4D unsigned integer vector + UInt4, + /// 归一化的 4 字节颜色(RGBA) | Normalized 4-byte color (RGBA) + UByte4Norm, +} + +impl VertexAttributeType { + /// 获取字节大小 + /// + /// Get byte size. + #[inline] + pub const fn byte_size(&self) -> usize { + match self { + Self::Float | Self::Int | Self::UInt => 4, + Self::Float2 | Self::Int2 | Self::UInt2 => 8, + Self::Float3 | Self::Int3 | Self::UInt3 => 12, + Self::Float4 | Self::Int4 | Self::UInt4 => 16, + Self::UByte4Norm => 4, + } + } + + /// 获取组件数量 + /// + /// Get component count. + #[inline] + pub const fn component_count(&self) -> u32 { + match self { + Self::Float | Self::Int | Self::UInt => 1, + Self::Float2 | Self::Int2 | Self::UInt2 => 2, + Self::Float3 | Self::Int3 | Self::UInt3 => 3, + Self::Float4 | Self::Int4 | Self::UInt4 | Self::UByte4Norm => 4, + } + } + + /// 是否为浮点类型 + /// + /// Check if float type. + #[inline] + pub const fn is_float(&self) -> bool { + matches!(self, Self::Float | Self::Float2 | Self::Float3 | Self::Float4) + } + + /// 是否为整数类型 + /// + /// Check if integer type. + #[inline] + pub const fn is_integer(&self) -> bool { + matches!( + self, + Self::Int | Self::Int2 | Self::Int3 | Self::Int4 | + Self::UInt | Self::UInt2 | Self::UInt3 | Self::UInt4 + ) + } +} + +/// 顶点属性描述 +/// +/// Vertex attribute descriptor. +#[derive(Debug, Clone)] +pub struct VertexAttribute { + /// 属性名称(对应 shader 中的 attribute) + /// + /// Attribute name (corresponds to shader attribute). + pub name: &'static str, + + /// 属性类型 + /// + /// Attribute type. + pub attr_type: VertexAttributeType, + + /// 字节偏移 + /// + /// Byte offset in vertex. + pub offset: usize, + + /// 是否归一化(仅对整数类型有效) + /// + /// Whether to normalize (only valid for integer types). + pub normalized: bool, +} + +impl VertexAttribute { + /// 创建新的顶点属性 + /// + /// Create new vertex attribute. + pub const fn new( + name: &'static str, + attr_type: VertexAttributeType, + offset: usize, + ) -> Self { + Self { + name, + attr_type, + offset, + normalized: false, + } + } + + /// 创建归一化的顶点属性 + /// + /// Create normalized vertex attribute. + pub const fn normalized( + name: &'static str, + attr_type: VertexAttributeType, + offset: usize, + ) -> Self { + Self { + name, + attr_type, + offset, + normalized: true, + } + } +} + +/// 顶点布局描述 +/// +/// Vertex layout descriptor. +#[derive(Debug, Clone)] +pub struct VertexLayout { + /// 属性列表 + /// + /// Attribute list. + pub attributes: Vec, + + /// 步幅(单个顶点字节大小) + /// + /// Stride (bytes per vertex). + pub stride: usize, +} + +impl VertexLayout { + /// 创建新的顶点布局 + /// + /// Create new vertex layout. + pub fn new(attributes: Vec, stride: usize) -> Self { + Self { attributes, stride } + } + + /// 从属性列表自动计算步幅 + /// + /// Auto-calculate stride from attribute list. + pub fn from_attributes(attributes: Vec) -> Self { + let stride = attributes + .iter() + .map(|attr| attr.offset + attr.attr_type.byte_size()) + .max() + .unwrap_or(0); + Self { attributes, stride } + } + + /// 创建 Sprite 顶点布局 + /// + /// 布局:position(2) + texcoord(2) + color(4) + aspect(1) = 9 floats = 36 bytes + /// + /// Create sprite vertex layout. + /// Layout: position(2) + texcoord(2) + color(4) + aspect(1) = 9 floats = 36 bytes + pub fn sprite() -> Self { + Self { + attributes: vec![ + VertexAttribute::new("a_position", VertexAttributeType::Float2, 0), + VertexAttribute::new("a_texcoord", VertexAttributeType::Float2, 8), + VertexAttribute::new("a_color", VertexAttributeType::Float4, 16), + VertexAttribute::new("a_aspect", VertexAttributeType::Float, 32), + ], + stride: 36, + } + } + + /// 创建简单 2D 顶点布局(位置 + 颜色) + /// + /// Create simple 2D vertex layout (position + color). + pub fn simple_2d() -> Self { + Self { + attributes: vec![ + VertexAttribute::new("a_position", VertexAttributeType::Float2, 0), + VertexAttribute::new("a_color", VertexAttributeType::Float4, 8), + ], + stride: 24, + } + } + + /// 创建带纹理的 2D 顶点布局(位置 + 纹理坐标 + 颜色) + /// + /// Create textured 2D vertex layout (position + texcoord + color). + pub fn textured_2d() -> Self { + Self { + attributes: vec![ + VertexAttribute::new("a_position", VertexAttributeType::Float2, 0), + VertexAttribute::new("a_texcoord", VertexAttributeType::Float2, 8), + VertexAttribute::new("a_color", VertexAttributeType::Float4, 16), + ], + stride: 32, + } + } +} + +// ==================== 预定义顶点结构 | Predefined Vertex Structures ==================== + +/// 精灵顶点 +/// +/// Sprite vertex. +/// +/// 与现有 SpriteBatch 顶点格式兼容。 +/// Compatible with existing SpriteBatch vertex format. +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, Pod, Zeroable)] +pub struct SpriteVertex { + /// 位置 | Position + pub position: [f32; 2], + /// 纹理坐标 | Texture coordinates + pub texcoord: [f32; 2], + /// 颜色(RGBA) | Color (RGBA) + pub color: [f32; 4], + /// 宽高比(用于保持纹理比例) | Aspect ratio (for maintaining texture ratio) + pub aspect: f32, +} + +impl SpriteVertex { + /// 顶点布局 + /// + /// Vertex layout. + pub fn layout() -> VertexLayout { + VertexLayout::sprite() + } +} + +/// 简单 2D 顶点(位置 + 颜色) +/// +/// Simple 2D vertex (position + color). +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, Pod, Zeroable)] +pub struct Simple2DVertex { + /// 位置 | Position + pub position: [f32; 2], + /// 颜色(RGBA) | Color (RGBA) + pub color: [f32; 4], +} + +impl Simple2DVertex { + /// 创建新顶点 + /// + /// Create new vertex. + pub const fn new(x: f32, y: f32, r: f32, g: f32, b: f32, a: f32) -> Self { + Self { + position: [x, y], + color: [r, g, b, a], + } + } + + /// 顶点布局 + /// + /// Vertex layout. + pub fn layout() -> VertexLayout { + VertexLayout::simple_2d() + } +} + +/// 带纹理的 2D 顶点 +/// +/// Textured 2D vertex. +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, Pod, Zeroable)] +pub struct Textured2DVertex { + /// 位置 | Position + pub position: [f32; 2], + /// 纹理坐标 | Texture coordinates + pub texcoord: [f32; 2], + /// 颜色(RGBA) | Color (RGBA) + pub color: [f32; 4], +} + +impl Textured2DVertex { + /// 创建新顶点 + /// + /// Create new vertex. + pub const fn new(x: f32, y: f32, u: f32, v: f32, r: f32, g: f32, b: f32, a: f32) -> Self { + Self { + position: [x, y], + texcoord: [u, v], + color: [r, g, b, a], + } + } + + /// 顶点布局 + /// + /// Vertex layout. + pub fn layout() -> VertexLayout { + VertexLayout::textured_2d() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem; + + #[test] + fn test_sprite_vertex_size() { + assert_eq!(mem::size_of::(), 36); + } + + #[test] + fn test_simple_2d_vertex_size() { + assert_eq!(mem::size_of::(), 24); + } + + #[test] + fn test_textured_2d_vertex_size() { + assert_eq!(mem::size_of::(), 32); + } + + #[test] + fn test_vertex_layout_stride() { + let layout = VertexLayout::sprite(); + assert_eq!(layout.stride, 36); + assert_eq!(layout.attributes.len(), 4); + } +} diff --git a/packages/engine/Cargo.toml b/packages/engine/Cargo.toml index f6e14af5..8e3aeae4 100644 --- a/packages/engine/Cargo.toml +++ b/packages/engine/Cargo.toml @@ -16,6 +16,9 @@ crate-type = ["cdylib", "rlib"] default = ["console_error_panic_hook"] [dependencies] +# Shared types and traits | 共享类型和trait +es-engine-shared = { path = "../engine-shared" } + # WASM bindings | WASM绑定 wasm-bindgen = "0.2" js-sys = "0.3" diff --git a/packages/engine/src/backend/mod.rs b/packages/engine/src/backend/mod.rs new file mode 100644 index 00000000..9ce50679 --- /dev/null +++ b/packages/engine/src/backend/mod.rs @@ -0,0 +1,10 @@ +//! 图形后端实现 +//! +//! Graphics backend implementations. +//! +//! 本模块提供 `GraphicsBackend` trait 的具体实现。 +//! This module provides concrete implementations of the `GraphicsBackend` trait. + +mod webgl2; + +pub use webgl2::WebGL2Backend; diff --git a/packages/engine/src/backend/webgl2.rs b/packages/engine/src/backend/webgl2.rs new file mode 100644 index 00000000..6984ebf3 --- /dev/null +++ b/packages/engine/src/backend/webgl2.rs @@ -0,0 +1,962 @@ +//! WebGL2 后端实现 +//! +//! WebGL2 backend implementation. + +use es_engine_shared::{ + traits::backend::*, + types::{ + handle::*, + vertex::*, + blend::*, + texture::*, + }, + Vec2, Vec3, Vec4, Mat3, Mat4, +}; + +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::{ + WebGl2RenderingContext as GL, + WebGlProgram, WebGlShader, WebGlBuffer, + WebGlTexture, WebGlVertexArrayObject, + WebGlUniformLocation, HtmlCanvasElement, +}; +use std::collections::HashMap; + +// ==================== 内部数据结构 | Internal Data Structures ==================== + +/// 着色器数据 +/// +/// Shader data. +struct ShaderData { + /// WebGL 程序对象 | WebGL program object + program: WebGlProgram, + /// Uniform 位置缓存 | Uniform location cache + uniform_locations: HashMap>, +} + +/// 纹理数据 +/// +/// Texture data. +struct TextureData { + /// WebGL 纹理对象 | WebGL texture object + handle: WebGlTexture, + /// 纹理宽度 | Texture width + width: u32, + /// 纹理高度 | Texture height + height: u32, +} + +/// VAO 数据 +/// +/// VAO data. +struct VertexArrayData { + /// WebGL VAO 对象 | WebGL VAO object + vao: WebGlVertexArrayObject, + /// 关联的索引缓冲区类型 | Associated index buffer type + index_type: Option, +} + +/// 索引类型 +/// +/// Index type. +#[derive(Clone, Copy)] +enum IndexType { + U16, + U32, +} + +// ==================== WebGL2Backend ==================== + +/// WebGL2 图形后端 +/// +/// 实现 `GraphicsBackend` trait,提供 WebGL2 渲染能力。 +/// +/// WebGL2 graphics backend. +/// Implements `GraphicsBackend` trait for WebGL2 rendering. +pub struct WebGL2Backend { + /// WebGL2 渲染上下文 | WebGL2 rendering context + gl: GL, + + /// 画布元素(可选,外部上下文时为 None) + /// + /// Canvas element (None for external context). + #[allow(dead_code)] + canvas: Option, + + /// 当前宽度 | Current width + width: u32, + + /// 当前高度 | Current height + height: u32, + + // ===== 资源管理 | Resource Management ===== + + /// 缓冲区映射 | Buffer map + buffers: HandleMap, + + /// 顶点数组对象映射 | VAO map + vertex_arrays: HandleMap, + + /// 着色器映射 | Shader map + shaders: HandleMap, + + /// 纹理映射 | Texture map + textures: HandleMap, + + // ===== 当前状态 | Current State ===== + + /// 当前绑定的着色器 | Currently bound shader + current_shader: Option, + + /// 当前渲染状态 | Current render state + current_render_state: RenderState, + + /// 版本字符串 | Version string + version: String, +} + +impl WebGL2Backend { + /// 从 canvas ID 创建 + /// + /// Create from canvas ID. + pub fn from_canvas(canvas_id: &str) -> GraphicsResult { + let document = web_sys::window() + .ok_or_else(|| GraphicsError::Backend("No window".into()))? + .document() + .ok_or_else(|| GraphicsError::Backend("No document".into()))?; + + let canvas = document + .get_element_by_id(canvas_id) + .ok_or_else(|| GraphicsError::Backend(format!("Canvas '{}' not found", canvas_id)))? + .dyn_into::() + .map_err(|_| GraphicsError::Backend("Element is not a canvas".into()))?; + + let gl = canvas + .get_context("webgl2") + .map_err(|e| GraphicsError::Backend(format!("Failed to get WebGL2 context: {:?}", e)))? + .ok_or_else(|| GraphicsError::Backend("WebGL2 not supported".into()))? + .dyn_into::() + .map_err(|_| GraphicsError::Backend("Failed to cast to WebGL2".into()))?; + + let width = canvas.width(); + let height = canvas.height(); + + let version = gl + .get_parameter(GL::VERSION) + .ok() + .and_then(|v| v.as_string()) + .unwrap_or_else(|| "WebGL 2.0".to_string()); + + let mut backend = Self { + gl, + canvas: Some(canvas), + width, + height, + buffers: HandleMap::new(), + vertex_arrays: HandleMap::new(), + shaders: HandleMap::new(), + textures: HandleMap::new(), + current_shader: None, + current_render_state: RenderState::default(), + version, + }; + + backend.init_gl_state(); + Ok(backend) + } + + /// 从外部 GL 上下文创建(微信小游戏等) + /// + /// Create from external GL context (WeChat Mini Game, etc.). + pub fn from_external(gl_context: JsValue, width: u32, height: u32) -> GraphicsResult { + let gl = gl_context + .dyn_into::() + .map_err(|_| GraphicsError::Backend("Failed to cast to WebGL2".into()))?; + + let version = gl + .get_parameter(GL::VERSION) + .ok() + .and_then(|v| v.as_string()) + .unwrap_or_else(|| "WebGL 2.0".to_string()); + + let mut backend = Self { + gl, + canvas: None, + width, + height, + buffers: HandleMap::new(), + vertex_arrays: HandleMap::new(), + shaders: HandleMap::new(), + textures: HandleMap::new(), + current_shader: None, + current_render_state: RenderState::default(), + version, + }; + + backend.init_gl_state(); + Ok(backend) + } + + /// 初始化 GL 状态 + /// + /// Initialize GL state. + fn init_gl_state(&mut self) { + self.gl.viewport(0, 0, self.width as i32, self.height as i32); + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA); + } + + /// 获取 WebGL 上下文引用 + /// + /// Get WebGL context reference. + pub fn gl(&self) -> &GL { + &self.gl + } + + /// 获取或缓存 uniform 位置 + /// + /// Get or cache uniform location. + fn get_uniform_location(&mut self, name: &str) -> Option { + let shader_handle = self.current_shader?; + let shader = self.shaders.get_mut(shader_handle)?; + + if let Some(cached) = shader.uniform_locations.get(name) { + return cached.clone(); + } + + let location = self.gl.get_uniform_location(&shader.program, name); + shader.uniform_locations.insert(name.to_string(), location.clone()); + location + } + + /// 编译单个着色器 + /// + /// Compile single shader. + fn compile_shader_stage(&self, shader_type: u32, source: &str) -> GraphicsResult { + let shader = self.gl.create_shader(shader_type) + .ok_or_else(|| GraphicsError::ShaderCompilation("Failed to create shader".into()))?; + + self.gl.shader_source(&shader, source); + self.gl.compile_shader(&shader); + + if !self.gl.get_shader_parameter(&shader, GL::COMPILE_STATUS) + .as_bool() + .unwrap_or(false) + { + let log = self.gl.get_shader_info_log(&shader).unwrap_or_default(); + self.gl.delete_shader(Some(&shader)); + let stage_name = if shader_type == GL::VERTEX_SHADER { "Vertex" } else { "Fragment" }; + return Err(GraphicsError::ShaderCompilation(format!("{} shader: {}", stage_name, log))); + } + + Ok(shader) + } +} + +// ==================== GraphicsBackend 实现 | GraphicsBackend Implementation ==================== + +impl GraphicsBackend for WebGL2Backend { + fn name(&self) -> &'static str { + "WebGL2" + } + + fn version(&self) -> &str { + &self.version + } + + fn resize(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + self.gl.viewport(0, 0, width as i32, height as i32); + } + + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } + + fn begin_frame(&mut self) { + // WebGL2 不需要显式 begin frame + } + + fn end_frame(&mut self) { + // WebGL2 不需要显式 end frame(自动 swap) + } + + fn clear(&mut self, r: f32, g: f32, b: f32, a: f32) { + self.gl.clear_color(r, g, b, a); + self.gl.clear(GL::COLOR_BUFFER_BIT); + } + + fn set_viewport(&mut self, x: i32, y: i32, width: u32, height: u32) { + self.gl.viewport(x, y, width as i32, height as i32); + } + + // ==================== 缓冲区操作 | Buffer Operations ==================== + + fn create_vertex_buffer(&mut self, data: &[u8], usage: BufferUsage) -> GraphicsResult { + let buffer = self.gl.create_buffer() + .ok_or_else(|| GraphicsError::BufferCreation("Failed to create vertex buffer".into()))?; + + self.gl.bind_buffer(GL::ARRAY_BUFFER, Some(&buffer)); + + let gl_usage = match usage { + BufferUsage::Static => GL::STATIC_DRAW, + BufferUsage::Dynamic => GL::DYNAMIC_DRAW, + BufferUsage::Stream => GL::STREAM_DRAW, + }; + + if data.is_empty() { + // Allocate empty buffer - should not happen in normal use + self.gl.buffer_data_with_i32(GL::ARRAY_BUFFER, 0, gl_usage); + } else { + unsafe { + let array = js_sys::Uint8Array::view(data); + self.gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &array, gl_usage); + } + } + + Ok(self.buffers.insert(buffer)) + } + + fn create_vertex_buffer_sized(&mut self, size: usize, usage: BufferUsage) -> GraphicsResult { + let buffer = self.gl.create_buffer() + .ok_or_else(|| GraphicsError::BufferCreation("Failed to create vertex buffer".into()))?; + + self.gl.bind_buffer(GL::ARRAY_BUFFER, Some(&buffer)); + + let gl_usage = match usage { + BufferUsage::Static => GL::STATIC_DRAW, + BufferUsage::Dynamic => GL::DYNAMIC_DRAW, + BufferUsage::Stream => GL::STREAM_DRAW, + }; + + self.gl.buffer_data_with_i32(GL::ARRAY_BUFFER, size as i32, gl_usage); + + Ok(self.buffers.insert(buffer)) + } + + fn create_index_buffer(&mut self, data: &[u16], usage: BufferUsage) -> GraphicsResult { + let buffer = self.gl.create_buffer() + .ok_or_else(|| GraphicsError::BufferCreation("Failed to create index buffer".into()))?; + + self.gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, Some(&buffer)); + + let gl_usage = match usage { + BufferUsage::Static => GL::STATIC_DRAW, + BufferUsage::Dynamic => GL::DYNAMIC_DRAW, + BufferUsage::Stream => GL::STREAM_DRAW, + }; + + unsafe { + let array = js_sys::Uint16Array::view(data); + self.gl.buffer_data_with_array_buffer_view(GL::ELEMENT_ARRAY_BUFFER, &array, gl_usage); + } + + Ok(self.buffers.insert(buffer)) + } + + fn create_index_buffer_u32(&mut self, data: &[u32], usage: BufferUsage) -> GraphicsResult { + let buffer = self.gl.create_buffer() + .ok_or_else(|| GraphicsError::BufferCreation("Failed to create index buffer".into()))?; + + self.gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, Some(&buffer)); + + let gl_usage = match usage { + BufferUsage::Static => GL::STATIC_DRAW, + BufferUsage::Dynamic => GL::DYNAMIC_DRAW, + BufferUsage::Stream => GL::STREAM_DRAW, + }; + + unsafe { + let array = js_sys::Uint32Array::view(data); + self.gl.buffer_data_with_array_buffer_view(GL::ELEMENT_ARRAY_BUFFER, &array, gl_usage); + } + + Ok(self.buffers.insert(buffer)) + } + + fn update_buffer(&mut self, handle: BufferHandle, offset: usize, data: &[u8]) -> GraphicsResult<()> { + let buffer = self.buffers.get(handle) + .ok_or_else(|| GraphicsError::InvalidHandle("Buffer not found".into()))?; + + self.gl.bind_buffer(GL::ARRAY_BUFFER, Some(buffer)); + + unsafe { + let array = js_sys::Uint8Array::view(data); + self.gl.buffer_sub_data_with_i32_and_array_buffer_view( + GL::ARRAY_BUFFER, + offset as i32, + &array, + ); + } + + Ok(()) + } + + fn destroy_buffer(&mut self, handle: BufferHandle) { + if let Some(buffer) = self.buffers.remove(handle) { + self.gl.delete_buffer(Some(&buffer)); + } + } + + fn create_vertex_array( + &mut self, + vertex_buffer: BufferHandle, + index_buffer: Option, + layout: &VertexLayout, + ) -> GraphicsResult { + let vao = self.gl.create_vertex_array() + .ok_or_else(|| GraphicsError::BufferCreation("Failed to create VAO".into()))?; + + self.gl.bind_vertex_array(Some(&vao)); + + // 绑定顶点缓冲区 + let vb = self.buffers.get(vertex_buffer) + .ok_or_else(|| GraphicsError::InvalidHandle("Vertex buffer not found".into()))?; + self.gl.bind_buffer(GL::ARRAY_BUFFER, Some(vb)); + + // 设置顶点属性 + for (idx, attr) in layout.attributes.iter().enumerate() { + let (size, type_, normalized) = match attr.attr_type { + VertexAttributeType::Float => (1, GL::FLOAT, false), + VertexAttributeType::Float2 => (2, GL::FLOAT, false), + VertexAttributeType::Float3 => (3, GL::FLOAT, false), + VertexAttributeType::Float4 => (4, GL::FLOAT, false), + VertexAttributeType::Int => (1, GL::INT, false), + VertexAttributeType::Int2 => (2, GL::INT, false), + VertexAttributeType::Int3 => (3, GL::INT, false), + VertexAttributeType::Int4 => (4, GL::INT, false), + VertexAttributeType::UInt => (1, GL::UNSIGNED_INT, false), + VertexAttributeType::UInt2 => (2, GL::UNSIGNED_INT, false), + VertexAttributeType::UInt3 => (3, GL::UNSIGNED_INT, false), + VertexAttributeType::UInt4 => (4, GL::UNSIGNED_INT, false), + VertexAttributeType::UByte4Norm => (4, GL::UNSIGNED_BYTE, true), + }; + + self.gl.enable_vertex_attrib_array(idx as u32); + + if attr.attr_type.is_integer() && !normalized { + self.gl.vertex_attrib_i_pointer_with_i32( + idx as u32, + size, + type_, + layout.stride as i32, + attr.offset as i32, + ); + } else { + self.gl.vertex_attrib_pointer_with_i32( + idx as u32, + size, + type_, + normalized || attr.normalized, + layout.stride as i32, + attr.offset as i32, + ); + } + } + + let index_type = if let Some(ib_handle) = index_buffer { + if let Some(ib) = self.buffers.get(ib_handle) { + self.gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, Some(ib)); + Some(IndexType::U16) + } else { + self.gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, None); + None + } + } else { + self.gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, None); + None + }; + + self.gl.bind_vertex_array(None); + + let data = VertexArrayData { vao, index_type }; + Ok(self.vertex_arrays.insert(data)) + } + + fn destroy_vertex_array(&mut self, handle: VertexArrayHandle) { + if let Some(data) = self.vertex_arrays.remove(handle) { + self.gl.delete_vertex_array(Some(&data.vao)); + } + } + + // ==================== 着色器操作 | Shader Operations ==================== + + fn compile_shader(&mut self, vertex_src: &str, fragment_src: &str) -> GraphicsResult { + // 编译顶点着色器 + let vert_shader = self.compile_shader_stage(GL::VERTEX_SHADER, vertex_src)?; + + // 编译片段着色器 + let frag_shader = match self.compile_shader_stage(GL::FRAGMENT_SHADER, fragment_src) { + Ok(s) => s, + Err(e) => { + self.gl.delete_shader(Some(&vert_shader)); + return Err(e); + } + }; + + // 链接程序 + let program = self.gl.create_program() + .ok_or_else(|| GraphicsError::ShaderLinking("Failed to create program".into()))?; + + self.gl.attach_shader(&program, &vert_shader); + self.gl.attach_shader(&program, &frag_shader); + self.gl.link_program(&program); + + // 删除着色器对象(已链接到程序) + self.gl.delete_shader(Some(&vert_shader)); + self.gl.delete_shader(Some(&frag_shader)); + + if !self.gl.get_program_parameter(&program, GL::LINK_STATUS) + .as_bool() + .unwrap_or(false) + { + let log = self.gl.get_program_info_log(&program).unwrap_or_default(); + self.gl.delete_program(Some(&program)); + return Err(GraphicsError::ShaderLinking(log)); + } + + let shader_data = ShaderData { + program, + uniform_locations: HashMap::new(), + }; + + Ok(self.shaders.insert(shader_data)) + } + + fn destroy_shader(&mut self, handle: ShaderHandle) { + if let Some(data) = self.shaders.remove(handle) { + self.gl.delete_program(Some(&data.program)); + } + + // 如果销毁的是当前着色器,清除状态 + if self.current_shader == Some(handle) { + self.current_shader = None; + } + } + + fn bind_shader(&mut self, handle: ShaderHandle) -> GraphicsResult<()> { + let data = self.shaders.get(handle) + .ok_or_else(|| GraphicsError::InvalidHandle("Shader not found".into()))?; + + self.gl.use_program(Some(&data.program)); + self.current_shader = Some(handle); + Ok(()) + } + + fn set_uniform_f32(&mut self, name: &str, value: f32) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform1f(location.as_ref(), value); + Ok(()) + } + + fn set_uniform_vec2(&mut self, name: &str, value: Vec2) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform2f(location.as_ref(), value.x, value.y); + Ok(()) + } + + fn set_uniform_vec3(&mut self, name: &str, value: Vec3) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform3f(location.as_ref(), value.x, value.y, value.z); + Ok(()) + } + + fn set_uniform_vec4(&mut self, name: &str, value: Vec4) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform4f(location.as_ref(), value.x, value.y, value.z, value.w); + Ok(()) + } + + fn set_uniform_mat3(&mut self, name: &str, value: &Mat3) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform_matrix3fv_with_f32_array(location.as_ref(), false, &value.to_cols_array()); + Ok(()) + } + + fn set_uniform_mat4(&mut self, name: &str, value: &Mat4) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform_matrix4fv_with_f32_array(location.as_ref(), false, &value.to_cols_array()); + Ok(()) + } + + fn set_uniform_i32(&mut self, name: &str, value: i32) -> GraphicsResult<()> { + let location = self.get_uniform_location(name); + self.gl.uniform1i(location.as_ref(), value); + Ok(()) + } + + // ==================== 纹理操作 | Texture Operations ==================== + + fn create_texture(&mut self, desc: &TextureDescriptor) -> GraphicsResult { + let texture = self.gl.create_texture() + .ok_or_else(|| GraphicsError::TextureCreation("Failed to create texture".into()))?; + + self.gl.bind_texture(GL::TEXTURE_2D, Some(&texture)); + + // 设置过滤模式 + let min_filter = texture_filter_to_gl(desc.filter_min); + let mag_filter = texture_filter_to_gl(desc.filter_mag); + let wrap_s = texture_wrap_to_gl(desc.wrap_s); + let wrap_t = texture_wrap_to_gl(desc.wrap_t); + + self.gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, min_filter as i32); + self.gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, mag_filter as i32); + self.gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, wrap_s as i32); + self.gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, wrap_t as i32); + + let data = TextureData { + handle: texture, + width: desc.width, + height: desc.height, + }; + + Ok(self.textures.insert(data)) + } + + fn create_blank_texture(&mut self, width: u32, height: u32) -> GraphicsResult { + let desc = TextureDescriptor::new(width, height); + let handle = self.create_texture(&desc)?; + + // 分配空白纹理内存 + if let Some(data) = self.textures.get(handle) { + self.gl.bind_texture(GL::TEXTURE_2D, Some(&data.handle)); + self.gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + GL::TEXTURE_2D, + 0, + GL::RGBA as i32, + width as i32, + height as i32, + 0, + GL::RGBA, + GL::UNSIGNED_BYTE, + None, + ).map_err(|e| GraphicsError::TextureCreation(format!("{:?}", e)))?; + } + + Ok(handle) + } + + fn upload_texture_data( + &mut self, + handle: TextureHandle, + data: &[u8], + width: u32, + height: u32, + ) -> GraphicsResult<()> { + let tex_data = self.textures.get_mut(handle) + .ok_or_else(|| GraphicsError::InvalidHandle("Texture not found".into()))?; + + tex_data.width = width; + tex_data.height = height; + + self.gl.bind_texture(GL::TEXTURE_2D, Some(&tex_data.handle)); + + self.gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + GL::TEXTURE_2D, + 0, + GL::RGBA as i32, + width as i32, + height as i32, + 0, + GL::RGBA, + GL::UNSIGNED_BYTE, + Some(data), + ).map_err(|e| GraphicsError::TextureCreation(format!("{:?}", e)))?; + + Ok(()) + } + + fn update_texture_region( + &mut self, + handle: TextureHandle, + x: u32, + y: u32, + width: u32, + height: u32, + data: &[u8], + ) -> GraphicsResult<()> { + let tex_data = self.textures.get(handle) + .ok_or_else(|| GraphicsError::InvalidHandle("Texture not found".into()))?; + + self.gl.bind_texture(GL::TEXTURE_2D, Some(&tex_data.handle)); + + self.gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array( + GL::TEXTURE_2D, + 0, + x as i32, + y as i32, + width as i32, + height as i32, + GL::RGBA, + GL::UNSIGNED_BYTE, + Some(data), + ).map_err(|e| GraphicsError::TextureCreation(format!("{:?}", e)))?; + + Ok(()) + } + + fn destroy_texture(&mut self, handle: TextureHandle) { + if let Some(data) = self.textures.remove(handle) { + self.gl.delete_texture(Some(&data.handle)); + } + } + + fn bind_texture(&mut self, handle: TextureHandle, unit: u32) -> GraphicsResult<()> { + let data = self.textures.get(handle) + .ok_or_else(|| GraphicsError::InvalidHandle("Texture not found".into()))?; + + self.gl.active_texture(GL::TEXTURE0 + unit); + self.gl.bind_texture(GL::TEXTURE_2D, Some(&data.handle)); + Ok(()) + } + + fn get_texture_size(&self, handle: TextureHandle) -> Option<(u32, u32)> { + self.textures.get(handle).map(|d| (d.width, d.height)) + } + + // ==================== 渲染状态 | Render State ==================== + + fn apply_render_state(&mut self, state: &RenderState) { + if self.current_render_state.blend_mode != state.blend_mode { + self.set_blend_mode(state.blend_mode); + } + + if self.current_render_state.scissor != state.scissor { + self.set_scissor(state.scissor); + } + + self.current_render_state = state.clone(); + } + + fn set_blend_mode(&mut self, mode: BlendMode) { + match mode { + BlendMode::None => { + self.gl.disable(GL::BLEND); + } + BlendMode::Alpha => { + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA); + } + BlendMode::Additive => { + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::ONE, GL::ONE); + } + BlendMode::Multiply => { + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::DST_COLOR, GL::ZERO); + } + BlendMode::Screen => { + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::ONE, GL::ONE_MINUS_SRC_COLOR); + } + BlendMode::PremultipliedAlpha => { + self.gl.enable(GL::BLEND); + self.gl.blend_func(GL::ONE, GL::ONE_MINUS_SRC_ALPHA); + } + } + + self.current_render_state.blend_mode = mode; + } + + fn set_scissor(&mut self, rect: Option) { + match rect { + Some(r) => { + self.gl.enable(GL::SCISSOR_TEST); + // WebGL Y 轴翻转 + let gl_y = self.height as i32 - r.y - r.height as i32; + self.gl.scissor(r.x, gl_y, r.width as i32, r.height as i32); + } + None => { + self.gl.disable(GL::SCISSOR_TEST); + } + } + + self.current_render_state.scissor = rect; + } + + // ==================== 绘制命令 | Draw Commands ==================== + + fn draw_indexed( + &mut self, + vao: VertexArrayHandle, + index_count: u32, + index_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_elements_with_i32( + GL::TRIANGLES, + index_count as i32, + GL::UNSIGNED_SHORT, + (index_offset * 2) as i32, // 2 bytes per u16 + ); + + Ok(()) + } + + fn draw_indexed_u32( + &mut self, + vao: VertexArrayHandle, + index_count: u32, + index_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_elements_with_i32( + GL::TRIANGLES, + index_count as i32, + GL::UNSIGNED_INT, + (index_offset * 4) as i32, // 4 bytes per u32 + ); + + Ok(()) + } + + fn draw( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_arrays(GL::TRIANGLES, vertex_offset as i32, vertex_count as i32); + + Ok(()) + } + + fn draw_lines( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_arrays(GL::LINES, vertex_offset as i32, vertex_count as i32); + + Ok(()) + } + + fn draw_line_loop( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_arrays(GL::LINE_LOOP, vertex_offset as i32, vertex_count as i32); + + Ok(()) + } + + fn draw_line_strip( + &mut self, + vao: VertexArrayHandle, + vertex_count: u32, + vertex_offset: u32, + ) -> GraphicsResult<()> { + let vao_data = self.vertex_arrays.get(vao) + .ok_or_else(|| GraphicsError::InvalidHandle("VAO not found".into()))?; + + self.gl.bind_vertex_array(Some(&vao_data.vao)); + self.gl.draw_arrays(GL::LINE_STRIP, vertex_offset as i32, vertex_count as i32); + + Ok(()) + } + + // ==================== 查询 | Queries ==================== + + fn max_texture_size(&self) -> u32 { + self.gl.get_parameter(GL::MAX_TEXTURE_SIZE) + .ok() + .and_then(|v| v.as_f64()) + .map(|v| v as u32) + .unwrap_or(4096) + } + + fn supports_feature(&self, feature: GraphicsFeature) -> bool { + match feature { + GraphicsFeature::AnisotropicFiltering => { + self.gl.get_extension("EXT_texture_filter_anisotropic").is_ok() + } + GraphicsFeature::Instancing => true, // WebGL2 支持 + GraphicsFeature::ComputeShaders => false, // WebGL2 不支持 + GraphicsFeature::MultipleRenderTargets => true, // WebGL2 支持 + GraphicsFeature::FloatTextures => { + self.gl.get_extension("EXT_color_buffer_float").is_ok() + } + GraphicsFeature::WebGPU => false, + } + } + + fn max_texture_units(&self) -> u32 { + self.gl.get_parameter(GL::MAX_TEXTURE_IMAGE_UNITS) + .ok() + .and_then(|v| v.as_f64()) + .map(|v| v as u32) + .unwrap_or(16) + } + + fn max_vertex_attributes(&self) -> u32 { + self.gl.get_parameter(GL::MAX_VERTEX_ATTRIBS) + .ok() + .and_then(|v| v.as_f64()) + .map(|v| v as u32) + .unwrap_or(16) + } +} + +// ==================== WebGL2-Specific Extensions ==================== + +impl WebGL2Backend { + /// Bind a raw WebGlTexture to a texture unit (for TextureManager compatibility). + pub fn bind_texture_raw(&self, texture: Option<&WebGlTexture>, unit: u32) { + self.gl.active_texture(GL::TEXTURE0 + unit); + self.gl.bind_texture(GL::TEXTURE_2D, texture); + } +} + +// ==================== 辅助函数 | Helper Functions ==================== + +/// 纹理过滤转 GL 常量 +/// +/// Convert texture filter to GL constant. +fn texture_filter_to_gl(filter: TextureFilter) -> u32 { + match filter { + TextureFilter::Nearest => GL::NEAREST, + TextureFilter::Linear => GL::LINEAR, + TextureFilter::NearestMipmapNearest => GL::NEAREST_MIPMAP_NEAREST, + TextureFilter::LinearMipmapNearest => GL::LINEAR_MIPMAP_NEAREST, + TextureFilter::NearestMipmapLinear => GL::NEAREST_MIPMAP_LINEAR, + TextureFilter::LinearMipmapLinear => GL::LINEAR_MIPMAP_LINEAR, + } +} + +/// 纹理环绕转 GL 常量 +/// +/// Convert texture wrap to GL constant. +fn texture_wrap_to_gl(wrap: TextureWrap) -> u32 { + match wrap { + TextureWrap::ClampToEdge => GL::CLAMP_TO_EDGE, + TextureWrap::Repeat => GL::REPEAT, + TextureWrap::MirroredRepeat => GL::MIRRORED_REPEAT, + } +} diff --git a/packages/engine/src/core/engine.rs b/packages/engine/src/core/engine.rs index 898133cf..fd3af06f 100644 --- a/packages/engine/src/core/engine.rs +++ b/packages/engine/src/core/engine.rs @@ -5,9 +5,11 @@ use wasm_bindgen::prelude::*; use super::context::WebGLContext; use super::error::Result; +use crate::backend::WebGL2Backend; use crate::input::InputManager; use crate::renderer::{Renderer2D, GridRenderer, GizmoRenderer, TransformMode, ViewportManager}; use crate::resource::TextureManager; +use es_engine_shared::traits::backend::GraphicsBackend; /// Engine configuration options. /// 引擎配置选项。 @@ -41,6 +43,15 @@ pub struct Engine { /// WebGL上下文。 context: WebGLContext, + /// Graphics backend abstraction layer. + /// 图形后端抽象层。 + /// + /// Provides cross-platform graphics API abstraction. + /// Currently WebGL2, future support for wgpu/native. + /// 提供跨平台图形 API 抽象。 + /// 当前为 WebGL2,未来支持 wgpu/原生平台。 + backend: WebGL2Backend, + /// 2D renderer. /// 2D渲染器。 renderer: Renderer2D, @@ -104,17 +115,34 @@ impl Engine { context.set_viewport(); context.enable_blend(); - // Create subsystems | 创建子系统 - let renderer = Renderer2D::new(context.gl(), config.max_sprites)?; - let grid_renderer = GridRenderer::new(context.gl())?; - let gizmo_renderer = GizmoRenderer::new(context.gl())?; + // Create graphics backend abstraction | 创建图形后端抽象 + let backend = WebGL2Backend::from_canvas(canvas_id) + .map_err(|e| crate::core::error::EngineError::WebGLError( + format!("Failed to create graphics backend: {:?}", e) + ))?; + + log::info!( + "Graphics backend initialized: {} ({})", + backend.name(), + backend.version() + ); + let texture_manager = TextureManager::new(context.gl().clone()); let input_manager = InputManager::new(); + let mut backend = backend; + let renderer = Renderer2D::new(&mut backend, config.max_sprites) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + let grid_renderer = GridRenderer::new(&mut backend) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + let gizmo_renderer = GizmoRenderer::new(&mut backend) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + log::info!("Engine created successfully | 引擎创建成功"); Ok(Self { context, + backend, renderer, grid_renderer, gizmo_renderer, @@ -124,7 +152,7 @@ impl Engine { show_grid: true, viewport_manager: ViewportManager::new(), show_gizmos: true, - is_editor: true, // 默认为编辑器模式 | Default to editor mode + is_editor: true, }) } @@ -139,21 +167,39 @@ impl Engine { height: u32, config: EngineConfig, ) -> Result { - let context = WebGLContext::from_external(gl_context, width, height)?; + let context = WebGLContext::from_external(gl_context.clone(), width, height)?; context.set_viewport(); context.enable_blend(); - let renderer = Renderer2D::new(context.gl(), config.max_sprites)?; - let grid_renderer = GridRenderer::new(context.gl())?; - let gizmo_renderer = GizmoRenderer::new(context.gl())?; + // Create graphics backend from external context | 从外部上下文创建图形后端 + let backend = WebGL2Backend::from_external(gl_context, width, height) + .map_err(|e| crate::core::error::EngineError::WebGLError( + format!("Failed to create graphics backend: {:?}", e) + ))?; + + log::info!( + "Graphics backend initialized: {} ({})", + backend.name(), + backend.version() + ); + let texture_manager = TextureManager::new(context.gl().clone()); let input_manager = InputManager::new(); + let mut backend = backend; + let renderer = Renderer2D::new(&mut backend, config.max_sprites) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + let grid_renderer = GridRenderer::new(&mut backend) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + let gizmo_renderer = GizmoRenderer::new(&mut backend) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; + log::info!("Engine created from external context | 从外部上下文创建引擎"); Ok(Self { context, + backend, renderer, grid_renderer, gizmo_renderer, @@ -163,7 +209,7 @@ impl Engine { show_grid: true, viewport_manager: ViewportManager::new(), show_gizmos: true, - is_editor: true, // 默认为编辑器模式 | Default to editor mode + is_editor: true, }) } @@ -187,6 +233,50 @@ impl Engine { self.context.height() } + // ===== Graphics Backend API ===== + // ===== 图形后端 API ===== + + /// Get reference to the graphics backend. + /// 获取图形后端的引用。 + /// + /// Use this for low-level graphics operations through the abstraction layer. + /// 使用此方法通过抽象层进行低级图形操作。 + #[inline] + pub fn backend(&self) -> &WebGL2Backend { + &self.backend + } + + /// Get mutable reference to the graphics backend. + /// 获取图形后端的可变引用。 + /// + /// Use this for low-level graphics operations through the abstraction layer. + /// 使用此方法通过抽象层进行低级图形操作。 + #[inline] + pub fn backend_mut(&mut self) -> &mut WebGL2Backend { + &mut self.backend + } + + /// Get backend name (e.g., "WebGL2"). + /// 获取后端名称(如 "WebGL2")。 + #[inline] + pub fn backend_name(&self) -> &'static str { + self.backend.name() + } + + /// Get backend version string. + /// 获取后端版本字符串。 + #[inline] + pub fn backend_version(&self) -> &str { + self.backend.version() + } + + /// Get maximum texture size supported by the backend. + /// 获取后端支持的最大纹理尺寸。 + #[inline] + pub fn max_texture_size(&self) -> u32 { + self.backend.max_texture_size() + } + /// Submit sprite batch data for rendering. /// 提交精灵批次数据进行渲染。 pub fn submit_sprite_batch( @@ -197,41 +287,28 @@ impl Engine { colors: &[u32], material_ids: &[u32], ) -> Result<()> { - self.renderer.submit_batch( - transforms, - texture_ids, - uvs, - colors, - material_ids, - &self.texture_manager, - ) + self.renderer.submit_batch(transforms, texture_ids, uvs, colors, material_ids) + .map_err(|e| crate::core::error::EngineError::WebGLError(e)) } - /// Render the current frame. - /// 渲染当前帧。 pub fn render(&mut self) -> Result<()> { - // Clear background with clear color let [r, g, b, a] = self.renderer.get_clear_color(); self.context.clear(r, g, b, a); - // Render grid first (background) - only in editor mode - // 首先渲染网格(背景)- 仅在编辑器模式下 + let camera = self.renderer.camera().clone(); + if self.is_editor && self.show_grid { - self.grid_renderer.render(self.context.gl(), self.renderer.camera()); - self.grid_renderer.render_axes(self.context.gl(), self.renderer.camera()); + self.grid_renderer.render(&mut self.backend, &camera); + self.grid_renderer.render_axes(&mut self.backend, &camera); } - // Render sprites - self.renderer.render(self.context.gl(), &self.texture_manager)?; + self.renderer.render(&mut self.backend, &self.texture_manager) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; - // Render gizmos on top - only in editor mode - // 在顶部渲染 gizmos - 仅在编辑器模式下 if self.is_editor && self.show_gizmos { - self.gizmo_renderer.render(self.context.gl(), self.renderer.camera()); - // Render axis indicator in corner - // 在角落渲染坐标轴指示器 + self.gizmo_renderer.render(&mut self.backend, &camera); self.gizmo_renderer.render_axis_indicator( - self.context.gl(), + &mut self.backend, self.context.width() as f32, self.context.height() as f32, ); @@ -247,10 +324,8 @@ impl Engine { /// This is used for overlay rendering (e.g., UI layer on top of world). /// 用于叠加渲染(例如,UI 层叠加在世界上)。 pub fn render_overlay(&mut self) -> Result<()> { - // Render sprites without clearing - // 渲染精灵但不清屏 - self.renderer.render(self.context.gl(), &self.texture_manager)?; - Ok(()) + self.renderer.render(&mut self.backend, &self.texture_manager) + .map_err(|e| crate::core::error::EngineError::WebGLError(e)) } /// Set scissor rect for clipping (screen coordinates, Y-down). @@ -616,52 +691,37 @@ impl Engine { } } - /// Render to a specific viewport. - /// 渲染到特定视口。 pub fn render_to_viewport(&mut self, viewport_id: &str) -> Result<()> { let viewport = match self.viewport_manager.get(viewport_id) { Some(v) => v, None => return Ok(()), }; - // Get viewport settings let show_grid = viewport.config.show_grid; let show_gizmos = viewport.config.show_gizmos; let camera = viewport.camera.clone(); + let (vp_width, vp_height) = viewport.dimensions(); - // Bind viewport and clear viewport.bind(); viewport.clear(); - // Update renderer camera to match viewport camera let renderer_camera = self.renderer.camera_mut(); renderer_camera.position = camera.position; renderer_camera.set_zoom(camera.zoom); renderer_camera.rotation = camera.rotation; renderer_camera.set_viewport(camera.viewport_width(), camera.viewport_height()); - // Render grid if enabled - only in editor mode - // 渲染网格(如果启用)- 仅在编辑器模式下 if self.is_editor && show_grid { - self.grid_renderer.render(viewport.gl(), &camera); - self.grid_renderer.render_axes(viewport.gl(), &camera); + self.grid_renderer.render(&mut self.backend, &camera); + self.grid_renderer.render_axes(&mut self.backend, &camera); } - // Render sprites - self.renderer.render(viewport.gl(), &self.texture_manager)?; + self.renderer.render(&mut self.backend, &self.texture_manager) + .map_err(|e| crate::core::error::EngineError::WebGLError(e))?; - // Render gizmos if enabled - only in editor mode - // 渲染 gizmos(如果启用)- 仅在编辑器模式下 if self.is_editor && show_gizmos { - self.gizmo_renderer.render(viewport.gl(), &camera); - // Render axis indicator in corner - // 在角落渲染坐标轴指示器 - let (vp_width, vp_height) = viewport.dimensions(); - self.gizmo_renderer.render_axis_indicator( - viewport.gl(), - vp_width as f32, - vp_height as f32, - ); + self.gizmo_renderer.render(&mut self.backend, &camera); + self.gizmo_renderer.render_axis_indicator(&mut self.backend, vp_width as f32, vp_height as f32); } self.gizmo_renderer.clear(); @@ -684,7 +744,8 @@ impl Engine { vertex_source: &str, fragment_source: &str, ) -> Result { - self.renderer.compile_shader(self.context.gl(), vertex_source, fragment_source) + self.renderer.compile_shader(&mut self.backend, vertex_source, fragment_source) + .map_err(|e| crate::core::error::EngineError::WebGLError(e)) } /// Compile a shader with a specific ID. @@ -695,7 +756,8 @@ impl Engine { vertex_source: &str, fragment_source: &str, ) -> Result<()> { - self.renderer.compile_shader_with_id(self.context.gl(), shader_id, vertex_source, fragment_source) + self.renderer.compile_shader_with_id(&mut self.backend, shader_id, vertex_source, fragment_source) + .map_err(|e| crate::core::error::EngineError::WebGLError(e)) } /// Check if a shader exists. diff --git a/packages/engine/src/lib.rs b/packages/engine/src/lib.rs index dc05a932..f046d2af 100644 --- a/packages/engine/src/lib.rs +++ b/packages/engine/src/lib.rs @@ -35,6 +35,7 @@ use wasm_bindgen::prelude::*; // Module declarations | 模块声明 +pub mod backend; pub mod core; pub mod math; pub mod platform; @@ -45,6 +46,18 @@ pub mod input; // Re-exports | 重新导出 pub use crate::core::{Engine, EngineConfig}; pub use crate::core::error::{EngineError, Result}; +pub use crate::backend::WebGL2Backend; + +// Re-export shared types for convenience | 重新导出共享类型以方便使用 +pub use es_engine_shared::{ + traits::backend::{GraphicsBackend, GraphicsError, GraphicsResult, GraphicsFeature, BufferUsage}, + types::{ + handle::{Handle, HandleMap, BufferHandle, TextureHandle, ShaderHandle, VertexArrayHandle}, + vertex::{VertexLayout, VertexAttribute, VertexAttributeType, SpriteVertex}, + blend::{BlendMode, RenderState, ScissorRect}, + texture::{TextureDescriptor, TextureFormat, TextureFilter, TextureWrap}, + }, +}; /// Initialize panic hook for better error messages in console. /// 初始化panic hook以在控制台显示更好的错误信息。 @@ -819,4 +832,28 @@ impl GameEngine { .update_texture_region(id, x, y, width, height, pixels) .map_err(|e| JsValue::from_str(&e.to_string())) } + + // ===== Graphics Backend Info API ===== + // ===== 图形后端信息 API ===== + + /// Get the graphics backend name (e.g., "WebGL2"). + /// 获取图形后端名称(如 "WebGL2")。 + #[wasm_bindgen(js_name = getBackendName)] + pub fn get_backend_name(&self) -> String { + self.engine.backend_name().to_string() + } + + /// Get the graphics backend version string. + /// 获取图形后端版本字符串。 + #[wasm_bindgen(js_name = getBackendVersion)] + pub fn get_backend_version(&self) -> String { + self.engine.backend_version().to_string() + } + + /// Get maximum texture size supported by the backend. + /// 获取后端支持的最大纹理尺寸。 + #[wasm_bindgen(js_name = getMaxTextureSize)] + pub fn get_max_texture_size(&self) -> u32 { + self.engine.max_texture_size() + } } diff --git a/packages/engine/src/renderer/batch/sprite_batch.rs b/packages/engine/src/renderer/batch/sprite_batch.rs index b27f6640..28fba8b7 100644 --- a/packages/engine/src/renderer/batch/sprite_batch.rs +++ b/packages/engine/src/renderer/batch/sprite_batch.rs @@ -1,149 +1,65 @@ //! Sprite batch renderer for efficient 2D rendering. -//! 用于高效2D渲染的精灵批处理渲染器。 -use web_sys::{ - WebGl2RenderingContext, WebGlBuffer, WebGlVertexArrayObject, +use es_engine_shared::{ + traits::backend::{GraphicsBackend, BufferUsage}, + types::{ + handle::{BufferHandle, VertexArrayHandle}, + vertex::{VertexLayout, VertexAttribute, VertexAttributeType}, + }, }; - -use crate::core::error::{EngineError, Result}; use crate::math::Color; -use crate::resource::TextureManager; -use super::vertex::FLOATS_PER_VERTEX; -/// Number of vertices per sprite (quad). -/// 每个精灵的顶点数(四边形)。 const VERTICES_PER_SPRITE: usize = 4; - -/// Number of indices per sprite (2 triangles). -/// 每个精灵的索引数(2个三角形)。 const INDICES_PER_SPRITE: usize = 6; - -/// Transform data stride (x, y, rotation, scaleX, scaleY, originX, originY). -/// 变换数据步长。 +const FLOATS_PER_VERTEX: usize = 9; const TRANSFORM_STRIDE: usize = 7; - -/// UV data stride (u0, v0, u1, v1). -/// UV数据步长。 const UV_STRIDE: usize = 4; -/// Batch key combining material and texture IDs. -/// 组合材质ID和纹理ID的批次键。 #[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)] pub struct BatchKey { - /// Material ID (0 = default material). - /// 材质ID(0 = 默认材质)。 pub material_id: u32, - /// Texture ID. - /// 纹理ID。 pub texture_id: u32, } -/// Sprite batch renderer. -/// 精灵批处理渲染器。 -/// -/// Batches multiple sprites into a single draw call for optimal performance. -/// 将多个精灵合并为单次绘制调用以获得最佳性能。 -/// -/// # Performance | 性能 -/// - Uses dynamic vertex buffer for efficient updates | 使用动态顶点缓冲区以高效更新 -/// - Groups sprites by material and texture to minimize state changes | 按材质和纹理分组精灵以最小化状态更改 -/// - Supports up to 10000+ sprites per batch | 每批次支持10000+精灵 pub struct SpriteBatch { - /// Vertex array object. - /// 顶点数组对象。 - vao: WebGlVertexArrayObject, - - /// Vertex buffer object. - /// 顶点缓冲区对象。 - vbo: WebGlBuffer, - - /// Index buffer object. - /// 索引缓冲区对象。 - ibo: WebGlBuffer, - - /// Maximum number of sprites. - /// 最大精灵数。 + vbo: BufferHandle, + ibo: BufferHandle, + vao: VertexArrayHandle, max_sprites: usize, - - /// Batches stored as (key, vertices) pairs in submission order. - /// 按提交顺序存储的批次(键,顶点)对。 - /// - /// Only consecutive sprites with the same BatchKey are batched together. - /// Sprites with the same key but separated by different keys are kept in separate batches - /// to preserve correct render order. - /// 只有连续的相同 BatchKey 的 sprites 才会合批。 - /// 相同 key 但被其他 key 分隔的 sprites 保持在独立批次中以保证正确的渲染顺序。 batches: Vec<(BatchKey, Vec)>, - - /// Total sprite count across all batches. - /// 所有批次的总精灵数。 sprite_count: usize, - - /// Last batch key used, for determining if we can merge into the last batch. - /// 上一个使用的批次键,用于判断是否可以合并到最后一个批次。 last_batch_key: Option, } impl SpriteBatch { - /// Create a new sprite batch. - /// 创建新的精灵批处理器。 - /// - /// # Arguments | 参数 - /// * `gl` - WebGL2 context | WebGL2上下文 - /// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数 - pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result { - // Create VAO | 创建VAO - let vao = gl - .create_vertex_array() - .ok_or(EngineError::BufferCreationFailed)?; - gl.bind_vertex_array(Some(&vao)); - - // Create vertex buffer | 创建顶点缓冲区 - let vbo = gl - .create_buffer() - .ok_or(EngineError::BufferCreationFailed)?; - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&vbo)); - - // Allocate vertex buffer memory | 分配顶点缓冲区内存 + pub fn new(backend: &mut impl GraphicsBackend, max_sprites: usize) -> Result { let vertex_buffer_size = max_sprites * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX * 4; - gl.buffer_data_with_i32( - WebGl2RenderingContext::ARRAY_BUFFER, - vertex_buffer_size as i32, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - - // Create and populate index buffer | 创建并填充索引缓冲区 - let ibo = gl - .create_buffer() - .ok_or(EngineError::BufferCreationFailed)?; - gl.bind_buffer(WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER, Some(&ibo)); + let vbo = backend.create_vertex_buffer( + &vec![0u8; vertex_buffer_size], + BufferUsage::Dynamic, + ).map_err(|e| format!("VBO: {:?}", e))?; let indices = Self::generate_indices(max_sprites); - unsafe { - let index_array = js_sys::Uint16Array::view(&indices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER, - &index_array, - WebGl2RenderingContext::STATIC_DRAW, - ); - } + let ibo = backend.create_index_buffer( + bytemuck::cast_slice(&indices), + BufferUsage::Static, + ).map_err(|e| format!("IBO: {:?}", e))?; - // Set up vertex attributes | 设置顶点属性 - Self::setup_vertex_attributes(gl); + let layout = VertexLayout { + attributes: vec![ + VertexAttribute { name: "a_position".into(), attr_type: VertexAttributeType::Float2, offset: 0, normalized: false }, + VertexAttribute { name: "a_texcoord".into(), attr_type: VertexAttributeType::Float2, offset: 8, normalized: false }, + VertexAttribute { name: "a_color".into(), attr_type: VertexAttributeType::Float4, offset: 16, normalized: false }, + VertexAttribute { name: "a_aspect".into(), attr_type: VertexAttributeType::Float, offset: 32, normalized: false }, + ], + stride: FLOATS_PER_VERTEX * 4, + }; - // Unbind VAO | 解绑VAO - gl.bind_vertex_array(None); - - log::debug!( - "SpriteBatch created with capacity: {} sprites | SpriteBatch创建完成,容量: {}个精灵", - max_sprites, - max_sprites - ); + let vao = backend.create_vertex_array(vbo, Some(ibo), &layout) + .map_err(|e| format!("VAO: {:?}", e))?; Ok(Self { - vao, - vbo, - ibo, + vbo, ibo, vao, max_sprites, batches: Vec::new(), sprite_count: 0, @@ -151,104 +67,19 @@ impl SpriteBatch { }) } - /// Generate index buffer data. - /// 生成索引缓冲区数据。 fn generate_indices(max_sprites: usize) -> Vec { - let mut indices = Vec::with_capacity(max_sprites * INDICES_PER_SPRITE); - - for i in 0..max_sprites { + (0..max_sprites).flat_map(|i| { let base = (i * VERTICES_PER_SPRITE) as u16; - // Two triangles per sprite | 每个精灵两个三角形 - // Triangle 1: 0, 1, 2 | 三角形1 - // Triangle 2: 2, 3, 0 | 三角形2 - indices.push(base); - indices.push(base + 1); - indices.push(base + 2); - indices.push(base + 2); - indices.push(base + 3); - indices.push(base); - } - - indices + [base, base + 1, base + 2, base + 2, base + 3, base] + }).collect() } - /// Set up vertex attribute pointers. - /// 设置顶点属性指针。 - /// - /// Vertex layout (9 floats per vertex): - /// 顶点布局(每顶点 9 个浮点数): - /// - location 0: position (2 floats) - offset 0 - /// - location 1: tex_coord (2 floats) - offset 8 - /// - location 2: color (4 floats) - offset 16 - /// - location 3: aspect_ratio (1 float) - offset 32 - fn setup_vertex_attributes(gl: &WebGl2RenderingContext) { - let stride = (FLOATS_PER_VERTEX * 4) as i32; // 9 * 4 = 36 bytes - - // Position attribute (location = 0) | 位置属性 - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_with_i32( - 0, - 2, - WebGl2RenderingContext::FLOAT, - false, - stride, - 0, - ); - - // Texture coordinate attribute (location = 1) | 纹理坐标属性 - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_with_i32( - 1, - 2, - WebGl2RenderingContext::FLOAT, - false, - stride, - 8, // 2 floats * 4 bytes - ); - - // Color attribute (location = 2) | 颜色属性 - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_with_i32( - 2, - 4, - WebGl2RenderingContext::FLOAT, - false, - stride, - 16, // 4 floats * 4 bytes - ); - - // Aspect ratio attribute (location = 3) | 宽高比属性 - // Used by shaders for aspect-ratio-aware transformations - // 用于着色器中的宽高比感知变换 - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_with_i32( - 3, - 1, - WebGl2RenderingContext::FLOAT, - false, - stride, - 32, // (2 + 2 + 4) floats * 4 bytes - ); - } - - /// Clear the batch for a new frame. - /// 为新帧清空批处理。 pub fn clear(&mut self) { self.batches.clear(); self.sprite_count = 0; self.last_batch_key = None; } - /// Add sprites from batch data. - /// 从批处理数据添加精灵。 - /// - /// # Arguments | 参数 - /// * `transforms` - [x, y, rotation, scaleX, scaleY, originX, originY] per sprite - /// * `texture_ids` - Texture ID for each sprite | 每个精灵的纹理ID - /// * `uvs` - [u0, v0, u1, v1] per sprite | 每个精灵的UV坐标 - /// * `colors` - Packed RGBA color per sprite | 每个精灵的打包RGBA颜色 - /// * `material_ids` - Material ID for each sprite (0 = default) | 每个精灵的材质ID(0 = 默认) - /// * `_texture_manager` - Texture manager for getting texture sizes | 纹理管理器 pub fn add_sprites( &mut self, transforms: &[f32], @@ -256,252 +87,106 @@ impl SpriteBatch { uvs: &[f32], colors: &[u32], material_ids: &[u32], - _texture_manager: &TextureManager, - ) -> Result<()> { - let sprite_count = texture_ids.len(); + ) -> Result<(), String> { + let count = texture_ids.len(); - // Validate input data | 验证输入数据 - if transforms.len() != sprite_count * TRANSFORM_STRIDE { - return Err(EngineError::InvalidBatchData(format!( - "Transform data length mismatch: expected {}, got {}", - sprite_count * TRANSFORM_STRIDE, - transforms.len() - ))); + if transforms.len() != count * TRANSFORM_STRIDE { + return Err(format!("Transform mismatch: {} vs {}", transforms.len(), count * TRANSFORM_STRIDE)); + } + if uvs.len() != count * UV_STRIDE { + return Err(format!("UV mismatch: {} vs {}", uvs.len(), count * UV_STRIDE)); + } + if colors.len() != count || material_ids.len() != count { + return Err("Color/material count mismatch".into()); + } + if self.sprite_count + count > self.max_sprites { + return Err(format!("Batch overflow: {} + {} > {}", self.sprite_count, count, self.max_sprites)); } - if uvs.len() != sprite_count * UV_STRIDE { - return Err(EngineError::InvalidBatchData(format!( - "UV data length mismatch: expected {}, got {}", - sprite_count * UV_STRIDE, - uvs.len() - ))); - } + for i in 0..count { + let t = i * TRANSFORM_STRIDE; + let uv = i * UV_STRIDE; - if colors.len() != sprite_count { - return Err(EngineError::InvalidBatchData(format!( - "Color data length mismatch: expected {}, got {}", - sprite_count, - colors.len() - ))); - } - - if material_ids.len() != sprite_count { - return Err(EngineError::InvalidBatchData(format!( - "Material ID data length mismatch: expected {}, got {}", - sprite_count, - material_ids.len() - ))); - } - - // Check capacity | 检查容量 - if self.sprite_count + sprite_count > self.max_sprites { - return Err(EngineError::InvalidBatchData(format!( - "Batch capacity exceeded: {} + {} > {}", - self.sprite_count, sprite_count, self.max_sprites - ))); - } - - // Add each sprite grouped by material and texture | 按材质和纹理分组添加每个精灵 - for i in 0..sprite_count { - let t_offset = i * TRANSFORM_STRIDE; - let uv_offset = i * UV_STRIDE; - - let x = transforms[t_offset]; - let y = transforms[t_offset + 1]; - let rotation = transforms[t_offset + 2]; - let scale_x = transforms[t_offset + 3]; - let scale_y = transforms[t_offset + 4]; - let origin_x = transforms[t_offset + 5]; - let origin_y = transforms[t_offset + 6]; - - let u0 = uvs[uv_offset]; - let v0 = uvs[uv_offset + 1]; - let u1 = uvs[uv_offset + 2]; - let v1 = uvs[uv_offset + 3]; + let (x, y) = (transforms[t], transforms[t + 1]); + let rotation = transforms[t + 2]; + let (width, height) = (transforms[t + 3], transforms[t + 4]); + let (origin_x, origin_y) = (transforms[t + 5], transforms[t + 6]); + let (u0, v0, u1, v1) = (uvs[uv], uvs[uv + 1], uvs[uv + 2], uvs[uv + 3]); let color = Color::from_packed(colors[i]); let color_arr = [color.r, color.g, color.b, color.a]; + let aspect = if height.abs() > 0.001 { width / height } else { 1.0 }; - // scale_x and scale_y are the actual display dimensions - // scale_x 和 scale_y 是实际显示尺寸 - let width = scale_x; - let height = scale_y; + let key = BatchKey { material_id: material_ids[i], texture_id: texture_ids[i] }; - // Calculate aspect ratio (width / height), default 1.0 for degenerate cases - // 计算宽高比(宽度/高度),退化情况下默认为 1.0 - let aspect_ratio = if height.abs() > 0.001 { - width / height - } else { - 1.0 - }; - - let batch_key = BatchKey { - material_id: material_ids[i], - texture_id: texture_ids[i], - }; - - // Only batch consecutive sprites with the same key to preserve render order - // 只对连续相同 key 的 sprites 合批以保持渲染顺序 - let should_create_new_batch = match self.last_batch_key { - Some(last_key) => batch_key != last_key, - None => true, - }; - - if should_create_new_batch { - // Create a new batch | 创建新批次 - self.batches.push((batch_key, Vec::new())); - self.last_batch_key = Some(batch_key); + if self.last_batch_key != Some(key) { + self.batches.push((key, Vec::new())); + self.last_batch_key = Some(key); } - // Add to the last batch | 添加到最后一个批次 let batch = &mut self.batches.last_mut().unwrap().1; - - // Calculate transformed vertices and add to batch | 计算变换后的顶点并添加到批次 - Self::add_sprite_vertices_to_batch( - batch, - x, y, width, height, rotation, origin_x, origin_y, - u0, v0, u1, v1, color_arr, aspect_ratio, - ); + Self::add_sprite_vertices(batch, x, y, width, height, rotation, origin_x, origin_y, + u0, v0, u1, v1, color_arr, aspect); } - self.sprite_count += sprite_count; + self.sprite_count += count; Ok(()) } - /// Add vertices for a single sprite to a batch. - /// 为单个精灵添加顶点到批次。 - /// - /// Each vertex contains: position(2) + tex_coord(2) + color(4) + aspect_ratio(1) = 9 floats - /// 每个顶点包含: 位置(2) + 纹理坐标(2) + 颜色(4) + 宽高比(1) = 9 个浮点数 #[inline] - fn add_sprite_vertices_to_batch( + fn add_sprite_vertices( batch: &mut Vec, - x: f32, - y: f32, - width: f32, - height: f32, - rotation: f32, - origin_x: f32, - origin_y: f32, - u0: f32, - v0: f32, - u1: f32, - v1: f32, - color: [f32; 4], - aspect_ratio: f32, + x: f32, y: f32, width: f32, height: f32, rotation: f32, + origin_x: f32, origin_y: f32, + u0: f32, v0: f32, u1: f32, v1: f32, + color: [f32; 4], aspect: f32, ) { - let cos = rotation.cos(); - let sin = rotation.sin(); + let (cos, sin) = (rotation.cos(), rotation.sin()); + let (ox, oy) = (origin_x * width, origin_y * height); - // Origin offset | 原点偏移 - // origin (0,0) = bottom-left, (1,1) = top-right - // 原点 (0,0) = 左下角, (1,1) = 右上角 - let ox = origin_x * width; - let oy = origin_y * height; + let corners = [(-ox, height - oy), (width - ox, height - oy), (width - ox, -oy), (-ox, -oy)]; + let tex_coords = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]]; - // Local corner positions (relative to origin) | 局部角点位置(相对于原点) - // Y-up coordinate system | Y向上坐标系 - let corners = [ - (-ox, height - oy), // Top-left | 左上 - (width - ox, height - oy), // Top-right | 右上 - (width - ox, -oy), // Bottom-right | 右下 - (-ox, -oy), // Bottom-left | 左下 - ]; - - // UV coordinates use image coordinate system (top-left origin, Y-down) - // UV坐标使用图像坐标系(左上角为原点,Y轴向下) - // Incoming UV: [u0, v0, u1, v1] where v0 < v1 - // 传入的 UV:[u0, v0, u1, v1] 其中 v0 < v1 - let tex_coords = [ - [u0, v0], // Top-left - [u1, v0], // Top-right - [u1, v1], // Bottom-right - [u0, v1], // Bottom-left - ]; - - // Transform and add each vertex | 变换并添加每个顶点 for i in 0..4 { let (lx, ly) = corners[i]; - - // Apply rotation | 应用旋转 - let rx = lx * cos - ly * sin; - let ry = lx * sin + ly * cos; - - // Apply translation | 应用平移 - let px = rx + x; - let py = ry + y; - - // Position | 位置 - batch.push(px); - batch.push(py); - - // Texture coordinates | 纹理坐标 - batch.push(tex_coords[i][0]); - batch.push(tex_coords[i][1]); - - // Color | 颜色 + let (rx, ry) = (lx * cos - ly * sin, lx * sin + ly * cos); + batch.extend_from_slice(&[rx + x, ry + y]); + batch.extend_from_slice(&tex_coords[i]); batch.extend_from_slice(&color); - - // Aspect ratio (same for all 4 vertices of a quad) - // 宽高比(四边形的 4 个顶点相同) - batch.push(aspect_ratio); + batch.push(aspect); } } - /// Flush a batch to GPU and render. - /// 将批次刷新到GPU并渲染。 - fn flush_batch(&self, gl: &WebGl2RenderingContext, vertices: &[f32]) { - if vertices.is_empty() { - return; - } - - let sprite_count = vertices.len() / (VERTICES_PER_SPRITE * FLOATS_PER_VERTEX); - - // Bind VAO | 绑定VAO - gl.bind_vertex_array(Some(&self.vao)); - - // Upload vertex data | 上传顶点数据 - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vbo)); - unsafe { - let vertex_array = js_sys::Float32Array::view(vertices); - gl.buffer_sub_data_with_i32_and_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - 0, - &vertex_array, - ); - } - - // Draw | 绘制 - let index_count = (sprite_count * INDICES_PER_SPRITE) as i32; - gl.draw_elements_with_i32( - WebGl2RenderingContext::TRIANGLES, - index_count, - WebGl2RenderingContext::UNSIGNED_SHORT, - 0, - ); - - // Unbind VAO | 解绑VAO - gl.bind_vertex_array(None); - } - - /// Get all batches for rendering (in submission order). - /// 获取所有批次用于渲染(按提交顺序)。 pub fn batches(&self) -> &[(BatchKey, Vec)] { &self.batches } - /// Flush a specific batch by index. - /// 按索引刷新特定批次。 - pub fn flush_batch_at(&self, gl: &WebGl2RenderingContext, index: usize) { + pub fn flush_batch(&self, backend: &mut impl GraphicsBackend, vertices: &[f32]) { + if vertices.is_empty() { return; } + + let sprite_count = vertices.len() / (VERTICES_PER_SPRITE * FLOATS_PER_VERTEX); + backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(vertices)).ok(); + backend.draw_indexed(self.vao, (sprite_count * INDICES_PER_SPRITE) as u32, 0).ok(); + } + + pub fn flush_batch_at(&self, backend: &mut impl GraphicsBackend, index: usize) { if let Some((_, vertices)) = self.batches.get(index) { - self.flush_batch(gl, vertices); + self.flush_batch(backend, vertices); } } - /// Get current sprite count. - /// 获取当前精灵数量。 #[inline] pub fn sprite_count(&self) -> usize { self.sprite_count } + + pub fn vao(&self) -> VertexArrayHandle { + self.vao + } + + pub fn destroy(self, backend: &mut impl GraphicsBackend) { + backend.destroy_vertex_array(self.vao); + backend.destroy_buffer(self.vbo); + backend.destroy_buffer(self.ibo); + } } diff --git a/packages/engine/src/renderer/gizmo.rs b/packages/engine/src/renderer/gizmo.rs index bf76b1c3..e2ccd45b 100644 --- a/packages/engine/src/renderer/gizmo.rs +++ b/packages/engine/src/renderer/gizmo.rs @@ -1,98 +1,104 @@ //! Gizmo renderer for editor overlays. -//! 编辑器叠加层的Gizmo渲染器。 -use web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram}; -use crate::core::error::{Result, EngineError}; +use es_engine_shared::{ + traits::backend::{GraphicsBackend, BufferUsage}, + types::{ + handle::{ShaderHandle, BufferHandle, VertexArrayHandle}, + vertex::{VertexLayout, VertexAttribute, VertexAttributeType}, + blend::BlendMode, + }, + Vec4, Mat3, +}; use super::camera::Camera2D; +use std::f32::consts::PI; -const GIZMO_VERTEX_SHADER: &str = r#"#version 300 es +const VERTEX_SHADER: &str = r#"#version 300 es precision highp float; - layout(location = 0) in vec2 a_position; - uniform mat3 u_projection; - void main() { vec3 pos = u_projection * vec3(a_position, 1.0); gl_Position = vec4(pos.xy, 0.0, 1.0); } "#; -const GIZMO_FRAGMENT_SHADER: &str = r#"#version 300 es +const FRAGMENT_SHADER: &str = r#"#version 300 es precision highp float; - uniform vec4 u_color; - out vec4 fragColor; - void main() { fragColor = u_color; } "#; -/// Transform tool mode. -/// 变换工具模式。 -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +const X_AXIS_COLOR: Vec4 = Vec4::new(1.0, 0.3, 0.3, 1.0); +const Y_AXIS_COLOR: Vec4 = Vec4::new(0.3, 1.0, 0.3, 1.0); +const ROTATE_COLOR: Vec4 = Vec4::new(0.3, 0.6, 1.0, 1.0); +const SCALE_COLOR: Vec4 = Vec4::new(1.0, 0.8, 0.2, 1.0); + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum TransformMode { - /// Selection mode - show bounds only + #[default] Select, - /// Move mode - show translation arrows Move, - /// Rotate mode - show rotation circle Rotate, - /// Scale mode - show scale handles Scale, } -impl Default for TransformMode { - fn default() -> Self { - TransformMode::Select - } -} - -/// Gizmo renderer for drawing editor overlays like selection bounds. -/// 用于绘制编辑器叠加层(如选择边界)的Gizmo渲染器。 pub struct GizmoRenderer { - program: WebGlProgram, - vertex_buffer: WebGlBuffer, - /// Pending rectangle data: [x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, show_handles] - /// 待渲染的矩形数据 - rects: Vec, - /// Pending circle data: [x, y, radius, r, g, b, a, segments] - /// 待渲染的圆形数据 - circles: Vec, - /// Pending line data: stored as separate line commands - /// 待渲染的线条数据 + shader: ShaderHandle, + vbo: BufferHandle, + vao: VertexArrayHandle, + rects: Vec, + circles: Vec, lines: Vec, - /// Pending capsule data: [x, y, radius, half_height, rotation, r, g, b, a] - /// 待渲染的胶囊数据 - capsules: Vec, - /// Current transform mode + capsules: Vec, transform_mode: TransformMode, } -/// Line gizmo data -/// 线条 gizmo 数据 -struct LineGizmo { - points: Vec, - r: f32, - g: f32, - b: f32, - a: f32, - closed: bool, +struct RectGizmo { + x: f32, y: f32, width: f32, height: f32, + rotation: f32, origin_x: f32, origin_y: f32, + color: Vec4, show_handles: bool, } +struct CircleGizmo { + x: f32, y: f32, radius: f32, color: Vec4, segments: u32, +} + +struct LineGizmo { + points: Vec, color: Vec4, closed: bool, +} + +struct CapsuleGizmo { + x: f32, y: f32, radius: f32, half_height: f32, rotation: f32, color: Vec4, +} + +const MAX_GIZMO_VERTICES: usize = 4000; + impl GizmoRenderer { - /// Create a new gizmo renderer. - /// 创建新的Gizmo渲染器。 - pub fn new(gl: &WebGl2RenderingContext) -> Result { - let program = Self::create_program(gl)?; - let vertex_buffer = gl.create_buffer() - .ok_or(EngineError::BufferCreationFailed)?; + pub fn new(backend: &mut impl GraphicsBackend) -> Result { + let shader = backend.compile_shader(VERTEX_SHADER, FRAGMENT_SHADER) + .map_err(|e| format!("Gizmo shader: {:?}", e))?; + + let layout = VertexLayout { + attributes: vec![VertexAttribute { + name: "a_position".into(), + attr_type: VertexAttributeType::Float2, + offset: 0, + normalized: false, + }], + stride: 8, + }; + + let buffer_size = MAX_GIZMO_VERTICES * 2 * 4; + let vbo = backend.create_vertex_buffer_sized(buffer_size, BufferUsage::Dynamic) + .map_err(|e| format!("Gizmo VBO: {:?}", e))?; + let vao = backend.create_vertex_array(vbo, None, &layout) + .map_err(|e| format!("Gizmo VAO: {:?}", e))?; Ok(Self { - program, - vertex_buffer, + shader, vbo, vao, rects: Vec::new(), circles: Vec::new(), lines: Vec::new(), @@ -101,55 +107,6 @@ impl GizmoRenderer { }) } - fn create_program(gl: &WebGl2RenderingContext) -> Result { - let vert_shader = gl.create_shader(WebGl2RenderingContext::VERTEX_SHADER) - .ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create vertex shader".into()))?; - gl.shader_source(&vert_shader, GIZMO_VERTEX_SHADER); - gl.compile_shader(&vert_shader); - - if !gl.get_shader_parameter(&vert_shader, WebGl2RenderingContext::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_shader_info_log(&vert_shader).unwrap_or_default(); - return Err(EngineError::ShaderCompileFailed(format!("Gizmo vertex shader: {}", log))); - } - - let frag_shader = gl.create_shader(WebGl2RenderingContext::FRAGMENT_SHADER) - .ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create fragment shader".into()))?; - gl.shader_source(&frag_shader, GIZMO_FRAGMENT_SHADER); - gl.compile_shader(&frag_shader); - - if !gl.get_shader_parameter(&frag_shader, WebGl2RenderingContext::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_shader_info_log(&frag_shader).unwrap_or_default(); - return Err(EngineError::ShaderCompileFailed(format!("Gizmo fragment shader: {}", log))); - } - - let program = gl.create_program() - .ok_or_else(|| EngineError::ProgramLinkFailed("Failed to create gizmo program".into()))?; - gl.attach_shader(&program, &vert_shader); - gl.attach_shader(&program, &frag_shader); - gl.link_program(&program); - - if !gl.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_program_info_log(&program).unwrap_or_default(); - return Err(EngineError::ProgramLinkFailed(format!("Gizmo program: {}", log))); - } - - gl.delete_shader(Some(&vert_shader)); - gl.delete_shader(Some(&frag_shader)); - - Ok(program) - } - - /// Clear all pending gizmos. - /// 清空所有待渲染的Gizmo。 pub fn clear(&mut self) { self.rects.clear(); self.circles.clear(); @@ -157,666 +114,235 @@ impl GizmoRenderer { self.capsules.clear(); } - /// Add a rectangle outline gizmo. - /// 添加矩形边框Gizmo。 - /// - /// # Arguments | 参数 - /// * `x` - Center X position | 中心X位置 - /// * `y` - Center Y position | 中心Y位置 - /// * `width` - Rectangle width | 矩形宽度 - /// * `height` - Rectangle height | 矩形高度 - /// * `rotation` - Rotation in radians | 旋转角度(弧度) - /// * `origin_x` - Origin X (0-1) | 原点X (0-1) - /// * `origin_y` - Origin Y (0-1) | 原点Y (0-1) - /// * `r`, `g`, `b`, `a` - Color | 颜色 - /// * `show_handles` - Whether to show transform handles | 是否显示变换手柄 - pub fn add_rect( - &mut self, - x: f32, - y: f32, - width: f32, - height: f32, - rotation: f32, - origin_x: f32, - origin_y: f32, - r: f32, - g: f32, - b: f32, - a: f32, - show_handles: bool, - ) { - self.rects.extend_from_slice(&[ - x, y, width, height, rotation, origin_x, origin_y, r, g, b, a, if show_handles { 1.0 } else { 0.0 } - ]); + pub fn add_rect(&mut self, x: f32, y: f32, width: f32, height: f32, rotation: f32, + origin_x: f32, origin_y: f32, r: f32, g: f32, b: f32, a: f32, show_handles: bool) { + self.rects.push(RectGizmo { + x, y, width, height, rotation, origin_x, origin_y, + color: Vec4::new(r, g, b, a), show_handles, + }); } - /// Add a circle outline gizmo. - /// 添加圆形边框Gizmo。 - pub fn add_circle( - &mut self, - x: f32, - y: f32, - radius: f32, - r: f32, - g: f32, - b: f32, - a: f32, - ) { - self.circles.extend_from_slice(&[x, y, radius, r, g, b, a, 32.0]); + pub fn add_circle(&mut self, x: f32, y: f32, radius: f32, r: f32, g: f32, b: f32, a: f32) { + self.circles.push(CircleGizmo { x, y, radius, color: Vec4::new(r, g, b, a), segments: 32 }); } - /// Add a line gizmo. - /// 添加线条Gizmo。 - pub fn add_line( - &mut self, - points: Vec, - r: f32, - g: f32, - b: f32, - a: f32, - closed: bool, - ) { - self.lines.push(LineGizmo { points, r, g, b, a, closed }); + pub fn add_line(&mut self, points: Vec, r: f32, g: f32, b: f32, a: f32, closed: bool) { + self.lines.push(LineGizmo { points, color: Vec4::new(r, g, b, a), closed }); } - /// Add a capsule outline gizmo. - /// 添加胶囊边框Gizmo。 - /// - /// Capsule is defined by center position, radius, half-height (distance from center to cap centers), and rotation. - /// 胶囊由中心位置、半径、半高度(从中心到端帽圆心的距离)和旋转定义。 - pub fn add_capsule( - &mut self, - x: f32, - y: f32, - radius: f32, - half_height: f32, - rotation: f32, - r: f32, - g: f32, - b: f32, - a: f32, - ) { - self.capsules.extend_from_slice(&[x, y, radius, half_height, rotation, r, g, b, a]); + pub fn add_capsule(&mut self, x: f32, y: f32, radius: f32, half_height: f32, rotation: f32, + r: f32, g: f32, b: f32, a: f32) { + self.capsules.push(CapsuleGizmo { x, y, radius, half_height, rotation, color: Vec4::new(r, g, b, a) }); } - /// Render axis indicator at bottom-left corner of the viewport. - /// 在视口左下角渲染坐标轴指示器。 - /// - /// This is drawn in screen space and is not affected by camera pan/zoom. - /// 这是在屏幕空间绘制的,不受相机平移/缩放影响。 - pub fn render_axis_indicator( - &self, - gl: &WebGl2RenderingContext, - viewport_width: f32, - viewport_height: f32, - ) { - // Skip if viewport is too small - if viewport_width < 100.0 || viewport_height < 100.0 { - return; - } - - gl.use_program(Some(&self.program)); - - // Disable depth test for screen-space UI - gl.disable(WebGl2RenderingContext::DEPTH_TEST); - - // Create orthographic projection for screen space (NDC: -1 to 1) - let half_w = viewport_width / 2.0; - let half_h = viewport_height / 2.0; - - let projection = [ - 1.0 / half_w, 0.0, 0.0, - 0.0, 1.0 / half_h, 0.0, - 0.0, 0.0, 1.0, - ]; - - let proj_loc = gl.get_uniform_location(&self.program, "u_projection"); - gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection); - - let color_loc = gl.get_uniform_location(&self.program, "u_color"); - - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer)); - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0); - - // Position in bottom-left corner - // 位置在左下角 - let padding = 35.0; - let center_x = -half_w + padding; - let center_y = -half_h + padding; - let axis_length = 25.0; - let arrow_size = 6.0; - let label_offset = 8.0; - let label_size = 3.5; - - // X axis (red, pointing right) - let x_end_x = center_x + axis_length; - let x_end_y = center_y; - - // X axis line - let x_axis = [ - center_x, center_y, - x_end_x - arrow_size * 0.3, x_end_y, - ]; - unsafe { - let array = js_sys::Float32Array::view(&x_axis); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 0.9, 0.2, 0.2, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2); - - // X arrow head (filled triangle) - let x_arrow = [ - x_end_x, x_end_y, - x_end_x - arrow_size, x_end_y + arrow_size * 0.35, - x_end_x - arrow_size, x_end_y - arrow_size * 0.35, - ]; - unsafe { - let array = js_sys::Float32Array::view(&x_arrow); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3); - - // X label - let lx = x_end_x + label_offset; - let ly = x_end_y; - let x_label = [ - lx - label_size, ly + label_size, - lx + label_size, ly - label_size, - lx - label_size, ly - label_size, - lx + label_size, ly + label_size, - ]; - unsafe { - let array = js_sys::Float32Array::view(&x_label); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 4); - - // Y axis (green, pointing up) - let y_end_x = center_x; - let y_end_y = center_y + axis_length; - - // Y axis line - let y_axis = [ - center_x, center_y, - y_end_x, y_end_y - arrow_size * 0.3, - ]; - unsafe { - let array = js_sys::Float32Array::view(&y_axis); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 0.26, 0.63, 0.28, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2); - - // Y arrow head (filled triangle) - let y_arrow = [ - y_end_x, y_end_y, - y_end_x - arrow_size * 0.35, y_end_y - arrow_size, - y_end_x + arrow_size * 0.35, y_end_y - arrow_size, - ]; - unsafe { - let array = js_sys::Float32Array::view(&y_arrow); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3); - - // Y label - let lx = y_end_x; - let ly = y_end_y + label_offset; - let y_label = [ - lx - label_size, ly + label_size, - lx, ly, - lx + label_size, ly + label_size, - lx, ly, - lx, ly, - lx, ly - label_size * 0.8, - ]; - unsafe { - let array = js_sys::Float32Array::view(&y_label); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 6); - - // Cleanup - gl.disable_vertex_attrib_array(0); - } - - /// Render all pending gizmos. - /// 渲染所有待渲染的Gizmo。 - pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) { - if self.rects.is_empty() && self.circles.is_empty() && self.lines.is_empty() && self.capsules.is_empty() { - return; - } - - gl.use_program(Some(&self.program)); - - let projection = camera.projection_matrix(); - let proj_loc = gl.get_uniform_location(&self.program, "u_projection"); - gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection.to_cols_array()); - - let color_loc = gl.get_uniform_location(&self.program, "u_color"); - - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer)); - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0); - - // Render rectangles - self.render_rects(gl, &color_loc, camera); - - // Render circles - self.render_circles(gl, &color_loc); - - // Render lines - self.render_lines(gl, &color_loc); - - // Render capsules - self.render_capsules(gl, &color_loc); - - gl.disable_vertex_attrib_array(0); - } - - /// Render all pending rectangles. - fn render_rects(&self, gl: &WebGl2RenderingContext, color_loc: &Option, camera: &Camera2D) { - let rect_stride = 12; - let rect_count = self.rects.len() / rect_stride; - - for i in 0..rect_count { - let offset = i * rect_stride; - let x = self.rects[offset]; - let y = self.rects[offset + 1]; - let width = self.rects[offset + 2]; - let height = self.rects[offset + 3]; - let rotation = self.rects[offset + 4]; - let origin_x = self.rects[offset + 5]; - let origin_y = self.rects[offset + 6]; - let r = self.rects[offset + 7]; - let g = self.rects[offset + 8]; - let b = self.rects[offset + 9]; - let a = self.rects[offset + 10]; - let show_handles = self.rects[offset + 11] > 0.5; - - let vertices = self.calculate_rect_vertices(x, y, width, height, rotation, origin_x, origin_y); - - unsafe { - let array = js_sys::Float32Array::view(&vertices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - - gl.uniform4f(color_loc.as_ref(), r, g, b, a); - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, 4); - - if show_handles { - match self.transform_mode { - TransformMode::Select => {} - TransformMode::Move => { - self.draw_move_handles(gl, color_loc, x, y, rotation, camera); - } - TransformMode::Rotate => { - self.draw_rotate_handles(gl, color_loc, x, y, width.max(height) * 0.6, camera); - } - TransformMode::Scale => { - self.draw_scale_handles(gl, color_loc, x, y, width, height, rotation, origin_x, origin_y, camera); - } - } - } - } - } - - /// Render all pending circles. - fn render_circles(&self, gl: &WebGl2RenderingContext, color_loc: &Option) { - let circle_stride = 8; - let circle_count = self.circles.len() / circle_stride; - - for i in 0..circle_count { - let offset = i * circle_stride; - let x = self.circles[offset]; - let y = self.circles[offset + 1]; - let radius = self.circles[offset + 2]; - let r = self.circles[offset + 3]; - let g = self.circles[offset + 4]; - let b = self.circles[offset + 5]; - let a = self.circles[offset + 6]; - let segments = self.circles[offset + 7] as usize; - - let mut vertices = Vec::with_capacity(segments * 2); - for j in 0..segments { - let angle = (j as f32 / segments as f32) * std::f32::consts::PI * 2.0; - vertices.push(x + radius * angle.cos()); - vertices.push(y + radius * angle.sin()); - } - - unsafe { - let array = js_sys::Float32Array::view(&vertices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - - gl.uniform4f(color_loc.as_ref(), r, g, b, a); - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, segments as i32); - } - } - - /// Render all pending lines. - fn render_lines(&self, gl: &WebGl2RenderingContext, color_loc: &Option) { - for line in &self.lines { - if line.points.len() < 4 { - continue; - } - - unsafe { - let array = js_sys::Float32Array::view(&line.points); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - - gl.uniform4f(color_loc.as_ref(), line.r, line.g, line.b, line.a); - let point_count = (line.points.len() / 2) as i32; - if line.closed { - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, point_count); - } else { - gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, point_count); - } - } - } - - /// Render all pending capsules. - fn render_capsules(&self, gl: &WebGl2RenderingContext, color_loc: &Option) { - let capsule_stride = 9; - let capsule_count = self.capsules.len() / capsule_stride; - let segments = 16; - - for i in 0..capsule_count { - let offset = i * capsule_stride; - let cx = self.capsules[offset]; - let cy = self.capsules[offset + 1]; - let radius = self.capsules[offset + 2]; - let half_height = self.capsules[offset + 3]; - let rotation = self.capsules[offset + 4]; - let r = self.capsules[offset + 5]; - let g = self.capsules[offset + 6]; - let b = self.capsules[offset + 7]; - let a = self.capsules[offset + 8]; - - let cos_r = rotation.cos(); - let sin_r = rotation.sin(); - - let mut vertices = Vec::with_capacity((segments * 2 + 4) * 2); - - // Draw capsule in local space then rotate: - // - Top semicircle at y = +half_height, arc from angle 0 to PI - // - Right line from top-right to bottom-right - // - Bottom semicircle at y = -half_height, arc from angle PI to 2*PI - // - Left line from bottom-left to top-left (closed by LINE_LOOP) - - // Top semicircle (arc curving upward) - for j in 0..=segments { - let angle = (j as f32 / segments as f32) * std::f32::consts::PI; - // Local coordinates: semicircle centered at (0, half_height) - // angle 0 -> (radius, half_height), angle PI -> (-radius, half_height) - // We want it to curve UP, so use cos for x, sin for y offset - let lx = radius * angle.cos(); - let ly = half_height + radius * angle.sin(); - // Rotate and translate to world - let wx = cx + lx * cos_r - ly * sin_r; - let wy = cy + lx * sin_r + ly * cos_r; - vertices.push(wx); - vertices.push(wy); - } - - // Bottom semicircle (arc curving downward) - for j in 0..=segments { - let angle = (j as f32 / segments as f32) * std::f32::consts::PI; - // Local coordinates: semicircle centered at (0, -half_height) - // angle 0 -> (-radius, -half_height), angle PI -> (radius, -half_height) - // We want it to curve DOWN, so negate both cos and sin offset - let lx = -radius * angle.cos(); - let ly = -half_height - radius * angle.sin(); - // Rotate and translate to world - let wx = cx + lx * cos_r - ly * sin_r; - let wy = cy + lx * sin_r + ly * cos_r; - vertices.push(wx); - vertices.push(wy); - } - - unsafe { - let array = js_sys::Float32Array::view(&vertices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - - gl.uniform4f(color_loc.as_ref(), r, g, b, a); - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, ((segments + 1) * 2) as i32); - } - } - - /// Set transform mode. - /// 设置变换模式。 pub fn set_transform_mode(&mut self, mode: TransformMode) { self.transform_mode = mode; } - /// Get transform mode. - /// 获取变换模式。 pub fn get_transform_mode(&self) -> TransformMode { self.transform_mode } - /// Draw move handles (arrows). - /// 绘制移动手柄(箭头)。 - fn draw_move_handles( - &self, - gl: &WebGl2RenderingContext, - color_loc: &Option, - x: f32, - y: f32, - rotation: f32, - camera: &Camera2D, - ) { - let arrow_length = 50.0 / camera.zoom; - let arrow_head = 10.0 / camera.zoom; - let cos = rotation.cos(); - let sin = rotation.sin(); - - // X axis (red) - let x_end_x = x + arrow_length * cos; - let x_end_y = y + arrow_length * sin; - let x_arrow = [ - x, y, - x_end_x, x_end_y, - x_end_x - arrow_head * cos + arrow_head * 0.3 * sin, - x_end_y - arrow_head * sin - arrow_head * 0.3 * cos, - x_end_x, x_end_y, - x_end_x - arrow_head * cos - arrow_head * 0.3 * sin, - x_end_y - arrow_head * sin + arrow_head * 0.3 * cos, - ]; - - unsafe { - let array = js_sys::Float32Array::view(&x_arrow); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); + pub fn render(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) { + if self.rects.is_empty() && self.circles.is_empty() && self.lines.is_empty() && self.capsules.is_empty() { + return; } - gl.uniform4f(color_loc.as_ref(), 1.0, 0.3, 0.3, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, 5); - // Y axis (green) - let y_end_x = x - arrow_length * sin; - let y_end_y = y + arrow_length * cos; - let y_arrow = [ - x, y, - y_end_x, y_end_y, - y_end_x + arrow_head * sin + arrow_head * 0.3 * cos, - y_end_y - arrow_head * cos + arrow_head * 0.3 * sin, - y_end_x, y_end_y, - y_end_x + arrow_head * sin - arrow_head * 0.3 * cos, - y_end_y - arrow_head * cos - arrow_head * 0.3 * sin, - ]; + backend.bind_shader(self.shader).ok(); + backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok(); + backend.set_blend_mode(BlendMode::Alpha); - unsafe { - let array = js_sys::Float32Array::view(&y_arrow); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 0.3, 1.0, 0.3, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINE_STRIP, 0, 5); + self.render_rects(backend, camera); + self.render_circles(backend); + self.render_lines(backend); + self.render_capsules(backend); } - /// Draw rotation handle (circle). - /// 绘制旋转手柄(圆形)。 - fn draw_rotate_handles( - &self, - gl: &WebGl2RenderingContext, - color_loc: &Option, - x: f32, - y: f32, - radius: f32, - _camera: &Camera2D, - ) { - let segments = 32; - let mut vertices = Vec::with_capacity(segments * 2); - - for i in 0..segments { - let angle = (i as f32 / segments as f32) * std::f32::consts::PI * 2.0; - vertices.push(x + radius * angle.cos()); - vertices.push(y + radius * angle.sin()); + pub fn render_axis_indicator(&mut self, backend: &mut impl GraphicsBackend, width: f32, height: f32) { + if width < 100.0 || height < 100.0 { + return; } - unsafe { - let array = js_sys::Float32Array::view(&vertices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 0.3, 0.6, 1.0, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, segments as i32); + backend.bind_shader(self.shader).ok(); + + let half_w = width / 2.0; + let half_h = height / 2.0; + let projection = Mat3::from_cols_array(&[ + 1.0 / half_w, 0.0, 0.0, + 0.0, 1.0 / half_h, 0.0, + 0.0, 0.0, 1.0, + ]); + backend.set_uniform_mat3("u_projection", &projection).ok(); + backend.set_blend_mode(BlendMode::Alpha); + + let padding = 35.0; + let cx = -half_w + padding; + let cy = -half_h + padding; + let axis_len = 25.0; + let arrow = 6.0; + let label_off = 8.0; + let label_sz = 3.5; + + // X axis + let x_end = cx + axis_len; + self.upload_and_draw_lines(backend, &[cx, cy, x_end - arrow * 0.3, cy], X_AXIS_COLOR); + self.upload_and_draw(backend, &[x_end, cy, x_end - arrow, cy + arrow * 0.35, x_end - arrow, cy - arrow * 0.35], X_AXIS_COLOR); + let lx = x_end + label_off; + self.upload_and_draw_lines(backend, &[lx - label_sz, cy + label_sz, lx + label_sz, cy - label_sz, + lx - label_sz, cy - label_sz, lx + label_sz, cy + label_sz], X_AXIS_COLOR); + + // Y axis + let y_end = cy + axis_len; + self.upload_and_draw_lines(backend, &[cx, cy, cx, y_end - arrow * 0.3], Y_AXIS_COLOR); + self.upload_and_draw(backend, &[cx, y_end, cx - arrow * 0.35, y_end - arrow, cx + arrow * 0.35, y_end - arrow], Y_AXIS_COLOR); + let ly = y_end + label_off; + self.upload_and_draw_lines(backend, &[cx - label_sz, ly + label_sz, cx, ly, cx + label_sz, ly + label_sz, cx, ly, + cx, ly, cx, ly - label_sz * 0.8], Y_AXIS_COLOR); } - /// Draw scale handles (squares at corners). - /// 绘制缩放手柄(角落的方块)。 - fn draw_scale_handles( - &self, - gl: &WebGl2RenderingContext, - color_loc: &Option, - x: f32, - y: f32, - width: f32, - height: f32, - rotation: f32, - origin_x: f32, - origin_y: f32, - camera: &Camera2D, - ) { - let handle_size = 6.0 / camera.zoom; - let corners = self.calculate_rect_vertices(x, y, width, height, rotation, origin_x, origin_y); + fn render_rects(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) { + let rects: Vec<_> = std::mem::take(&mut self.rects); + for rect in &rects { + let verts = Self::calc_rect_vertices(rect.x, rect.y, rect.width, rect.height, + rect.rotation, rect.origin_x, rect.origin_y); + self.upload_and_draw_line_loop(backend, &verts, rect.color); - // Draw a small square at each corner - for i in 0..4 { - let cx = corners[i * 2]; - let cy = corners[i * 2 + 1]; - - let square = [ - cx - handle_size, cy - handle_size, - cx + handle_size, cy - handle_size, - cx + handle_size, cy + handle_size, - cx - handle_size, cy + handle_size, - ]; - - unsafe { - let array = js_sys::Float32Array::view(&square); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); + if rect.show_handles { + match self.transform_mode { + TransformMode::Select => {} + TransformMode::Move => self.draw_move_handles(backend, rect.x, rect.y, rect.rotation, camera), + TransformMode::Rotate => self.draw_rotate_handles(backend, rect.x, rect.y, rect.width.max(rect.height) * 0.6), + TransformMode::Scale => self.draw_scale_handles(backend, &verts, camera), + } } - gl.uniform4f(color_loc.as_ref(), 1.0, 0.8, 0.2, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINE_LOOP, 0, 4); + } + self.rects = rects; + } + + fn render_circles(&mut self, backend: &mut impl GraphicsBackend) { + let circles: Vec<_> = std::mem::take(&mut self.circles); + for circle in &circles { + let verts = Self::build_circle(circle.x, circle.y, circle.radius, circle.segments); + self.upload_and_draw_line_loop(backend, &verts, circle.color); + } + self.circles = circles; + } + + fn render_lines(&mut self, backend: &mut impl GraphicsBackend) { + let lines: Vec<_> = std::mem::take(&mut self.lines); + for line in &lines { + if line.points.len() < 4 { continue; } + if line.closed { + self.upload_and_draw_line_loop(backend, &line.points, line.color); + } else { + self.upload_and_draw_line_strip(backend, &line.points, line.color); + } + } + self.lines = lines; + } + + fn render_capsules(&mut self, backend: &mut impl GraphicsBackend) { + const SEGMENTS: usize = 16; + let capsules: Vec<_> = std::mem::take(&mut self.capsules); + for cap in &capsules { + let (cos_r, sin_r) = (cap.rotation.cos(), cap.rotation.sin()); + let mut verts = Vec::with_capacity((SEGMENTS * 2 + 2) * 2); + + for j in 0..=SEGMENTS { + let angle = (j as f32 / SEGMENTS as f32) * PI; + let (lx, ly) = (cap.radius * angle.cos(), cap.half_height + cap.radius * angle.sin()); + verts.push(cap.x + lx * cos_r - ly * sin_r); + verts.push(cap.y + lx * sin_r + ly * cos_r); + } + for j in 0..=SEGMENTS { + let angle = (j as f32 / SEGMENTS as f32) * PI; + let (lx, ly) = (-cap.radius * angle.cos(), -cap.half_height - cap.radius * angle.sin()); + verts.push(cap.x + lx * cos_r - ly * sin_r); + verts.push(cap.y + lx * sin_r + ly * cos_r); + } + self.upload_and_draw_line_loop(backend, &verts, cap.color); + } + self.capsules = capsules; + } + + fn draw_move_handles(&mut self, backend: &mut impl GraphicsBackend, x: f32, y: f32, rotation: f32, camera: &Camera2D) { + let len = 50.0 / camera.zoom; + let head = 10.0 / camera.zoom; + let (cos, sin) = (rotation.cos(), rotation.sin()); + + let (xe, ye) = (x + len * cos, y + len * sin); + let x_arrow = [x, y, xe, ye, + xe - head * cos + head * 0.3 * sin, ye - head * sin - head * 0.3 * cos, xe, ye, + xe - head * cos - head * 0.3 * sin, ye - head * sin + head * 0.3 * cos]; + self.upload_and_draw_line_strip(backend, &x_arrow, X_AXIS_COLOR); + + let (xe2, ye2) = (x - len * sin, y + len * cos); + let y_arrow = [x, y, xe2, ye2, + xe2 + head * sin + head * 0.3 * cos, ye2 - head * cos + head * 0.3 * sin, xe2, ye2, + xe2 + head * sin - head * 0.3 * cos, ye2 - head * cos - head * 0.3 * sin]; + self.upload_and_draw_line_strip(backend, &y_arrow, Y_AXIS_COLOR); + } + + fn draw_rotate_handles(&mut self, backend: &mut impl GraphicsBackend, x: f32, y: f32, radius: f32) { + let verts = Self::build_circle(x, y, radius, 32); + self.upload_and_draw_line_loop(backend, &verts, ROTATE_COLOR); + } + + fn draw_scale_handles(&mut self, backend: &mut impl GraphicsBackend, corners: &[f32], camera: &Camera2D) { + let sz = 6.0 / camera.zoom; + for i in 0..4 { + let (cx, cy) = (corners[i * 2], corners[i * 2 + 1]); + let sq = [cx - sz, cy - sz, cx + sz, cy - sz, cx + sz, cy + sz, cx - sz, cy + sz]; + self.upload_and_draw_line_loop(backend, &sq, SCALE_COLOR); } } - /// Calculate the 4 corner vertices of a rotated rectangle. - /// 计算旋转矩形的4个角点顶点。 - fn calculate_rect_vertices( - &self, - x: f32, - y: f32, - width: f32, - height: f32, - rotation: f32, - origin_x: f32, - origin_y: f32, - ) -> [f32; 8] { - let cos = rotation.cos(); - let sin = rotation.sin(); + fn upload_and_draw(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) { + backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok(); + backend.set_uniform_vec4("u_color", color).ok(); + backend.draw(self.vao, (verts.len() / 2) as u32, 0).ok(); + } - // Origin offset - let ox = origin_x * width; - let oy = origin_y * height; + fn upload_and_draw_lines(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) { + backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok(); + backend.set_uniform_vec4("u_color", color).ok(); + backend.draw_lines(self.vao, (verts.len() / 2) as u32, 0).ok(); + } - // Local corner positions (relative to origin) - // Y-up coordinate system - let corners = [ - (-ox, height - oy), // Top-left - (width - ox, height - oy), // Top-right - (width - ox, -oy), // Bottom-right - (-ox, -oy), // Bottom-left - ]; + fn upload_and_draw_line_loop(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) { + backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok(); + backend.set_uniform_vec4("u_color", color).ok(); + backend.draw_line_loop(self.vao, (verts.len() / 2) as u32, 0).ok(); + } - let mut vertices = [0.0f32; 8]; + fn upload_and_draw_line_strip(&mut self, backend: &mut impl GraphicsBackend, verts: &[f32], color: Vec4) { + backend.update_buffer(self.vbo, 0, bytemuck::cast_slice(verts)).ok(); + backend.set_uniform_vec4("u_color", color).ok(); + backend.draw_line_strip(self.vao, (verts.len() / 2) as u32, 0).ok(); + } + + fn calc_rect_vertices(x: f32, y: f32, w: f32, h: f32, rot: f32, ox: f32, oy: f32) -> [f32; 8] { + let (cos, sin) = (rot.cos(), rot.sin()); + let (oxx, oyy) = (ox * w, oy * h); + let corners = [(-oxx, h - oyy), (w - oxx, h - oyy), (w - oxx, -oyy), (-oxx, -oyy)]; + let mut out = [0.0f32; 8]; for (i, (lx, ly)) in corners.iter().enumerate() { - // Apply rotation - let rx = lx * cos - ly * sin; - let ry = lx * sin + ly * cos; - - // Apply translation - vertices[i * 2] = rx + x; - vertices[i * 2 + 1] = ry + y; + out[i * 2] = lx * cos - ly * sin + x; + out[i * 2 + 1] = lx * sin + ly * cos + y; } + out + } - vertices + fn build_circle(x: f32, y: f32, r: f32, segments: u32) -> Vec { + (0..segments).flat_map(|i| { + let angle = (i as f32 / segments as f32) * PI * 2.0; + [x + r * angle.cos(), y + r * angle.sin()] + }).collect() + } + + pub fn destroy(self, backend: &mut impl GraphicsBackend) { + backend.destroy_vertex_array(self.vao); + backend.destroy_buffer(self.vbo); + backend.destroy_shader(self.shader); } } diff --git a/packages/engine/src/renderer/grid.rs b/packages/engine/src/renderer/grid.rs index b8cd9b6a..0d29e0e4 100644 --- a/packages/engine/src/renderer/grid.rs +++ b/packages/engine/src/renderer/grid.rs @@ -1,241 +1,210 @@ //! Grid renderer for editor viewport. -//! 编辑器视口的网格渲染器。 -use web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram}; -use crate::core::error::{Result, EngineError}; +use es_engine_shared::{ + traits::backend::{GraphicsBackend, BufferUsage}, + types::{ + handle::{ShaderHandle, BufferHandle, VertexArrayHandle}, + vertex::{VertexLayout, VertexAttribute, VertexAttributeType}, + blend::BlendMode, + }, + Vec4, +}; use super::camera::Camera2D; -const GRID_VERTEX_SHADER: &str = r#"#version 300 es +const VERTEX_SHADER: &str = r#"#version 300 es precision highp float; - layout(location = 0) in vec2 a_position; - uniform mat3 u_projection; - void main() { vec3 pos = u_projection * vec3(a_position, 1.0); gl_Position = vec4(pos.xy, 0.0, 1.0); } "#; -const GRID_FRAGMENT_SHADER: &str = r#"#version 300 es +const FRAGMENT_SHADER: &str = r#"#version 300 es precision highp float; - uniform vec4 u_color; - out vec4 fragColor; - void main() { fragColor = u_color; } "#; +const GRID_COLOR: Vec4 = Vec4::new(0.3, 0.3, 0.35, 1.0); +const X_AXIS_COLOR: Vec4 = Vec4::new(1.0, 0.3, 0.3, 1.0); +const Y_AXIS_COLOR: Vec4 = Vec4::new(0.3, 1.0, 0.3, 1.0); + pub struct GridRenderer { - program: WebGlProgram, - vertex_buffer: WebGlBuffer, - vertex_count: i32, - last_zoom: f32, - last_width: f32, - last_height: f32, + shader: ShaderHandle, + grid_vbo: BufferHandle, + grid_vao: VertexArrayHandle, + axis_vbo: BufferHandle, + axis_vao: VertexArrayHandle, + grid_vertex_count: u32, + cache: GridCache, } +#[derive(Default)] +struct GridCache { + zoom: f32, + width: f32, + height: f32, +} + +impl GridCache { + fn is_dirty(&self, camera: &Camera2D) -> bool { + (camera.zoom - self.zoom).abs() > 0.001 + || (camera.viewport_width() - self.width).abs() > 1.0 + || (camera.viewport_height() - self.height).abs() > 1.0 + } + + fn update(&mut self, camera: &Camera2D) { + self.zoom = camera.zoom; + self.width = camera.viewport_width(); + self.height = camera.viewport_height(); + } +} + +const MAX_GRID_VERTICES: usize = 8000; + impl GridRenderer { - pub fn new(gl: &WebGl2RenderingContext) -> Result { - let program = Self::create_program(gl)?; - let vertex_buffer = gl.create_buffer() - .ok_or(EngineError::BufferCreationFailed)?; + pub fn new(backend: &mut impl GraphicsBackend) -> Result { + let shader = backend.compile_shader(VERTEX_SHADER, FRAGMENT_SHADER) + .map_err(|e| format!("Grid shader: {:?}", e))?; + + let layout = VertexLayout { + attributes: vec![ + VertexAttribute { + name: "a_position".into(), + attr_type: VertexAttributeType::Float2, + offset: 0, + normalized: false, + }, + ], + stride: 8, + }; + + let grid_buffer_size = MAX_GRID_VERTICES * 2 * 4; + let grid_vbo = backend.create_vertex_buffer_sized(grid_buffer_size, BufferUsage::Dynamic) + .map_err(|e| format!("Grid VBO: {:?}", e))?; + let grid_vao = backend.create_vertex_array(grid_vbo, None, &layout) + .map_err(|e| format!("Grid VAO: {:?}", e))?; + + let axis_data = Self::build_axis_vertices(1000.0); + let axis_vbo = backend.create_vertex_buffer( + bytemuck::cast_slice(&axis_data), + BufferUsage::Dynamic, + ).map_err(|e| format!("Axis VBO: {:?}", e))?; + let axis_vao = backend.create_vertex_array(axis_vbo, None, &layout) + .map_err(|e| format!("Axis VAO: {:?}", e))?; Ok(Self { - program, - vertex_buffer, - vertex_count: 0, - last_zoom: 0.0, - last_width: 0.0, - last_height: 0.0, + shader, + grid_vbo, + grid_vao, + axis_vbo, + axis_vao, + grid_vertex_count: 0, + cache: GridCache::default(), }) } - fn create_program(gl: &WebGl2RenderingContext) -> Result { - let vert_shader = gl.create_shader(WebGl2RenderingContext::VERTEX_SHADER) - .ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create vertex shader".into()))?; - gl.shader_source(&vert_shader, GRID_VERTEX_SHADER); - gl.compile_shader(&vert_shader); + pub fn render(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) { + self.update_grid_if_needed(backend, camera); - if !gl.get_shader_parameter(&vert_shader, WebGl2RenderingContext::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_shader_info_log(&vert_shader).unwrap_or_default(); - return Err(EngineError::ShaderCompileFailed(format!("Grid vertex shader: {}", log))); - } - - let frag_shader = gl.create_shader(WebGl2RenderingContext::FRAGMENT_SHADER) - .ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create fragment shader".into()))?; - gl.shader_source(&frag_shader, GRID_FRAGMENT_SHADER); - gl.compile_shader(&frag_shader); - - if !gl.get_shader_parameter(&frag_shader, WebGl2RenderingContext::COMPILE_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_shader_info_log(&frag_shader).unwrap_or_default(); - return Err(EngineError::ShaderCompileFailed(format!("Grid fragment shader: {}", log))); - } - - let program = gl.create_program() - .ok_or_else(|| EngineError::ProgramLinkFailed("Failed to create grid program".into()))?; - gl.attach_shader(&program, &vert_shader); - gl.attach_shader(&program, &frag_shader); - gl.link_program(&program); - - if !gl.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS) - .as_bool() - .unwrap_or(false) - { - let log = gl.get_program_info_log(&program).unwrap_or_default(); - return Err(EngineError::ProgramLinkFailed(format!("Grid program: {}", log))); - } - - gl.delete_shader(Some(&vert_shader)); - gl.delete_shader(Some(&frag_shader)); - - Ok(program) - } - - fn update_grid(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) { - let zoom = camera.zoom; - let width = camera.viewport_width(); - let height = camera.viewport_height(); - - if (zoom - self.last_zoom).abs() < 0.001 - && (width - self.last_width).abs() < 1.0 - && (height - self.last_height).abs() < 1.0 - { + if self.grid_vertex_count == 0 { return; } - self.last_zoom = zoom; - self.last_width = width; - self.last_height = height; + backend.bind_shader(self.shader).ok(); + backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok(); + backend.set_uniform_vec4("u_color", GRID_COLOR).ok(); + backend.set_blend_mode(BlendMode::Alpha); + backend.draw_lines(self.grid_vao, self.grid_vertex_count, 0).ok(); + } - let half_width = width / (2.0 * zoom); - let half_height = height / (2.0 * zoom); - let max_size = half_width.max(half_height) * 2.0; + pub fn render_axes(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) { + let axis_length = self.calculate_axis_length(camera); + self.update_axis_buffer(backend, axis_length); - let base_step = if max_size > 10000.0 { - 1000.0 - } else if max_size > 1000.0 { - 100.0 - } else if max_size > 100.0 { - 10.0 - } else if max_size > 10.0 { - 1.0 - } else { - 0.1 - }; + backend.bind_shader(self.shader).ok(); + backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok(); + backend.set_blend_mode(BlendMode::Alpha); - let fine_step = base_step; + backend.set_uniform_vec4("u_color", X_AXIS_COLOR).ok(); + backend.draw_lines(self.axis_vao, 2, 0).ok(); + + backend.set_uniform_vec4("u_color", Y_AXIS_COLOR).ok(); + backend.draw_lines(self.axis_vao, 2, 2).ok(); + } + + fn update_grid_if_needed(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) { + if !self.cache.is_dirty(camera) { + return; + } + self.cache.update(camera); + + let vertices = self.build_grid_vertices(camera); + self.grid_vertex_count = (vertices.len() / 2) as u32; + + backend.update_buffer(self.grid_vbo, 0, bytemuck::cast_slice(&vertices)).ok(); + } + + fn build_grid_vertices(&self, camera: &Camera2D) -> Vec { + let half_w = camera.viewport_width() / (2.0 * camera.zoom); + let half_h = camera.viewport_height() / (2.0 * camera.zoom); + let max_size = half_w.max(half_h) * 2.0; + + let step = Self::calculate_grid_step(max_size); let range = max_size * 1.5; - let start = -range; - let end = range; let mut vertices = Vec::new(); + let start = (-range / step).floor() * step; + let end = (range / step).ceil() * step; - let mut x = (start / fine_step).floor() * fine_step; - while x <= end { - vertices.extend_from_slice(&[x, start, x, end]); - x += fine_step; + let mut pos = start; + while pos <= end { + vertices.extend_from_slice(&[pos, -range, pos, range]); + vertices.extend_from_slice(&[-range, pos, range, pos]); + pos += step; } - let mut y = (start / fine_step).floor() * fine_step; - while y <= end { - vertices.extend_from_slice(&[start, y, end, y]); - y += fine_step; - } + vertices + } - self.vertex_count = (vertices.len() / 2) as i32; - - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer)); - unsafe { - let array = js_sys::Float32Array::view(&vertices); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::DYNAMIC_DRAW, - ); + fn calculate_grid_step(max_size: f32) -> f32 { + match max_size { + s if s > 10000.0 => 1000.0, + s if s > 1000.0 => 100.0, + s if s > 100.0 => 10.0, + s if s > 10.0 => 1.0, + _ => 0.1, } } - pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) { - self.update_grid(gl, camera); - - if self.vertex_count == 0 { - return; - } - - gl.use_program(Some(&self.program)); - - let projection = camera.projection_matrix(); - let proj_loc = gl.get_uniform_location(&self.program, "u_projection"); - gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection.to_cols_array()); - - let color_loc = gl.get_uniform_location(&self.program, "u_color"); - gl.uniform4f(color_loc.as_ref(), 0.3, 0.3, 0.35, 1.0); - - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer)); - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0); - - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, self.vertex_count); - - gl.disable_vertex_attrib_array(0); + fn calculate_axis_length(&self, camera: &Camera2D) -> f32 { + let half_w = camera.viewport_width() / (2.0 * camera.zoom); + let half_h = camera.viewport_height() / (2.0 * camera.zoom); + half_w.max(half_h) * 2.0 } - pub fn render_axes(&self, gl: &WebGl2RenderingContext, camera: &Camera2D) { - gl.use_program(Some(&self.program)); + fn build_axis_vertices(length: f32) -> Vec { + vec![ + -length, 0.0, length, 0.0, + 0.0, -length, 0.0, length, + ] + } - let projection = camera.projection_matrix(); - let proj_loc = gl.get_uniform_location(&self.program, "u_projection"); - gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection.to_cols_array()); + fn update_axis_buffer(&mut self, backend: &mut impl GraphicsBackend, length: f32) { + let data = Self::build_axis_vertices(length); + backend.update_buffer(self.axis_vbo, 0, bytemuck::cast_slice(&data)).ok(); + } - let half_width = camera.viewport_width() / (2.0 * camera.zoom); - let half_height = camera.viewport_height() / (2.0 * camera.zoom); - let axis_length = half_width.max(half_height) * 2.0; - - let color_loc = gl.get_uniform_location(&self.program, "u_color"); - - let axis_buffer = gl.create_buffer().unwrap(); - gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&axis_buffer)); - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0); - - // X axis (red) - let x_axis = [-axis_length, 0.0, axis_length, 0.0f32]; - unsafe { - let array = js_sys::Float32Array::view(&x_axis); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::STATIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 1.0, 0.3, 0.3, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2); - - // Y axis (green) - let y_axis = [0.0, -axis_length, 0.0, axis_length]; - unsafe { - let array = js_sys::Float32Array::view(&y_axis); - gl.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &array, - WebGl2RenderingContext::STATIC_DRAW, - ); - } - gl.uniform4f(color_loc.as_ref(), 0.3, 1.0, 0.3, 1.0); - gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2); - - gl.disable_vertex_attrib_array(0); - gl.delete_buffer(Some(&axis_buffer)); + pub fn destroy(self, backend: &mut impl GraphicsBackend) { + backend.destroy_vertex_array(self.grid_vao); + backend.destroy_vertex_array(self.axis_vao); + backend.destroy_buffer(self.grid_vbo); + backend.destroy_buffer(self.axis_vbo); + backend.destroy_shader(self.shader); } } diff --git a/packages/engine/src/renderer/renderer2d.rs b/packages/engine/src/renderer/renderer2d.rs index e659e0aa..48b37963 100644 --- a/packages/engine/src/renderer/renderer2d.rs +++ b/packages/engine/src/renderer/renderer2d.rs @@ -1,110 +1,96 @@ //! Main 2D renderer implementation. -//! 主2D渲染器实现。 -use wasm_bindgen::JsCast; -use web_sys::WebGl2RenderingContext; - -use crate::core::error::Result; -use crate::resource::TextureManager; +use es_engine_shared::{ + traits::backend::GraphicsBackend, + types::{ + handle::ShaderHandle, + blend::ScissorRect, + }, +}; +use std::collections::HashMap; +use crate::backend::WebGL2Backend; use super::batch::SpriteBatch; use super::camera::Camera2D; -use super::shader::ShaderManager; -use super::material::MaterialManager; +use super::texture::TextureManager; +use super::material::{Material, BlendMode, UniformValue}; + +fn to_shared_blend_mode(mode: BlendMode) -> es_engine_shared::types::blend::BlendMode { + match mode { + BlendMode::None => es_engine_shared::types::blend::BlendMode::None, + BlendMode::Alpha => es_engine_shared::types::blend::BlendMode::Alpha, + BlendMode::Additive => es_engine_shared::types::blend::BlendMode::Additive, + BlendMode::Multiply => es_engine_shared::types::blend::BlendMode::Multiply, + BlendMode::Screen => es_engine_shared::types::blend::BlendMode::Screen, + BlendMode::PremultipliedAlpha => es_engine_shared::types::blend::BlendMode::PremultipliedAlpha, + } +} + +const SPRITE_VERTEX_SHADER: &str = r#"#version 300 es +precision highp float; +layout(location = 0) in vec2 a_position; +layout(location = 1) in vec2 a_texCoord; +layout(location = 2) in vec4 a_color; +uniform mat3 u_projection; +out vec2 v_texCoord; +out vec4 v_color; +void main() { + vec3 pos = u_projection * vec3(a_position, 1.0); + gl_Position = vec4(pos.xy, 0.0, 1.0); + v_texCoord = a_texCoord; + v_color = a_color; +} +"#; + +const SPRITE_FRAGMENT_SHADER: &str = r#"#version 300 es +precision highp float; +in vec2 v_texCoord; +in vec4 v_color; +uniform sampler2D u_texture; +out vec4 fragColor; +void main() { + vec4 texColor = texture(u_texture, v_texCoord); + fragColor = texColor * v_color; + if (fragColor.a < 0.01) discard; +} +"#; -/// 2D renderer with batched sprite rendering. -/// 带批处理精灵渲染的2D渲染器。 -/// -/// Coordinates sprite batching, shader management, and camera transforms. -/// 协调精灵批处理、Shader管理和相机变换。 pub struct Renderer2D { - /// Sprite batch renderer. - /// 精灵批处理渲染器。 sprite_batch: SpriteBatch, - - /// Shader manager. - /// 着色器管理器。 - shader_manager: ShaderManager, - - /// Material manager. - /// 材质管理器。 - material_manager: MaterialManager, - - /// 2D camera. - /// 2D相机。 + default_shader: ShaderHandle, + custom_shaders: HashMap, + next_shader_id: u32, + materials: HashMap, camera: Camera2D, - - /// Clear color (RGBA). - /// 清除颜色 (RGBA)。 clear_color: [f32; 4], - - /// Current active shader ID. - /// 当前激活的着色器ID。 - #[allow(dead_code)] - current_shader_id: u32, - - /// Current active material ID. - /// 当前激活的材质ID。 - #[allow(dead_code)] - current_material_id: u32, - - /// Current scissor rect (x, y, width, height) in screen coordinates. - /// None means scissor test is disabled. - /// 当前裁剪矩形(屏幕坐标)。None 表示禁用裁剪测试。 - scissor_rect: Option<[f32; 4]>, - - /// Viewport height for scissor coordinate conversion. - /// 视口高度,用于裁剪坐标转换。 + scissor_rect: Option, viewport_height: f32, } impl Renderer2D { - /// Create a new 2D renderer. - /// 创建新的2D渲染器。 - /// - /// # Arguments | 参数 - /// * `gl` - WebGL2 context | WebGL2上下文 - /// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数 - pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result { - let sprite_batch = SpriteBatch::new(gl, max_sprites)?; - let shader_manager = ShaderManager::new(gl)?; - let material_manager = MaterialManager::new(); + pub fn new(backend: &mut WebGL2Backend, max_sprites: usize) -> Result { + let sprite_batch = SpriteBatch::new(backend, max_sprites)?; + let default_shader = backend.compile_shader(SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER) + .map_err(|e| format!("Default shader: {:?}", e))?; - // Get canvas size for camera | 获取canvas尺寸用于相机 - let canvas = gl.canvas() - .and_then(|c| c.dyn_into::().ok()) - .map(|c| (c.width() as f32, c.height() as f32)) - .unwrap_or((800.0, 600.0)); + let (width, height) = (backend.width() as f32, backend.height() as f32); + let camera = Camera2D::new(width, height); - let camera = Camera2D::new(canvas.0, canvas.1); - - log::info!( - "Renderer2D initialized | Renderer2D初始化完成: {}x{}, max sprites: {}", - canvas.0, canvas.1, max_sprites - ); + let mut materials = HashMap::new(); + materials.insert(0, Material::default()); Ok(Self { sprite_batch, - shader_manager, - material_manager, + default_shader, + custom_shaders: HashMap::new(), + next_shader_id: 100, + materials, camera, clear_color: [0.1, 0.1, 0.12, 1.0], - current_shader_id: 0, - current_material_id: 0, scissor_rect: None, - viewport_height: canvas.1, + viewport_height: height, }) } - /// Submit sprite batch data for rendering. - /// 提交精灵批次数据进行渲染。 - /// - /// # Arguments | 参数 - /// * `transforms` - Transform data for each sprite | 每个精灵的变换数据 - /// * `texture_ids` - Texture ID for each sprite | 每个精灵的纹理ID - /// * `uvs` - UV coordinates for each sprite | 每个精灵的UV坐标 - /// * `colors` - Packed color for each sprite | 每个精灵的打包颜色 - /// * `material_ids` - Material ID for each sprite (0 = default) | 每个精灵的材质ID(0 = 默认) - /// * `texture_manager` - Texture manager | 纹理管理器 pub fn submit_batch( &mut self, transforms: &[f32], @@ -112,277 +98,171 @@ impl Renderer2D { uvs: &[f32], colors: &[u32], material_ids: &[u32], - texture_manager: &TextureManager, - ) -> Result<()> { - self.sprite_batch.add_sprites( - transforms, - texture_ids, - uvs, - colors, - material_ids, - texture_manager, - ) + ) -> Result<(), String> { + self.sprite_batch.add_sprites(transforms, texture_ids, uvs, colors, material_ids) } - /// Render the current frame. - /// 渲染当前帧。 - pub fn render(&mut self, gl: &WebGl2RenderingContext, texture_manager: &TextureManager) -> Result<()> { + pub fn render(&mut self, backend: &mut WebGL2Backend, texture_manager: &TextureManager) -> Result<(), String> { if self.sprite_batch.sprite_count() == 0 { return Ok(()); } - // Apply scissor test if enabled - // 如果启用,应用裁剪测试 - self.apply_scissor(gl); + self.apply_scissor(backend); - // Track current state to minimize state changes | 跟踪当前状态以最小化状态切换 - let mut current_material_id: u32 = u32::MAX; - let mut current_texture_id: u32 = u32::MAX; - - // Get projection matrix once | 一次性获取投影矩阵 let projection = self.camera.projection_matrix(); + let mut current_material_id = u32::MAX; + let mut current_texture_id = u32::MAX; - // Iterate through batches in submission order (preserves render order) - // 按提交顺序遍历批次(保持渲染顺序) for batch_idx in 0..self.sprite_batch.batches().len() { let (batch_key, vertices) = &self.sprite_batch.batches()[batch_idx]; + if vertices.is_empty() { continue; } - // Skip empty batches | 跳过空批次 - if vertices.is_empty() { - continue; - } - - // Switch material if needed | 如需切换材质 if batch_key.material_id != current_material_id { current_material_id = batch_key.material_id; - // Get material (fallback to default if not found) | 获取材质(未找到则回退到默认) - let material = self.material_manager.get_material(batch_key.material_id) - .unwrap_or_else(|| self.material_manager.get_default_material()); + let material = self.materials.get(&batch_key.material_id) + .cloned() + .unwrap_or_default(); - // Bind shader | 绑定Shader - let shader = self.shader_manager.get_shader(material.shader_id) - .unwrap_or_else(|| self.shader_manager.get_default_shader()); - shader.bind(gl); + let shader = if material.shader_id == 0 { + self.default_shader + } else { + self.custom_shaders.get(&material.shader_id) + .copied() + .unwrap_or(self.default_shader) + }; - // Apply blend mode | 应用混合模式 - MaterialManager::apply_blend_mode(gl, material.blend_mode); + backend.bind_shader(shader).ok(); + backend.set_blend_mode(to_shared_blend_mode(material.blend_mode)); + backend.set_uniform_mat3("u_projection", &projection).ok(); + backend.set_uniform_i32("u_texture", 0).ok(); - // Set projection matrix | 设置投影矩阵 - shader.set_uniform_mat3(gl, "u_projection", &projection.to_cols_array()); - - // Set texture sampler | 设置纹理采样器 - shader.set_uniform_i32(gl, "u_texture", 0); - - // Apply material uniforms | 应用材质uniform - material.uniforms.apply_to_shader(gl, shader); + for name in material.uniforms.names() { + if let Some(value) = material.uniforms.get(name) { + match value { + UniformValue::Float(v) => { backend.set_uniform_f32(name, *v).ok(); } + UniformValue::Vec2(v) => { backend.set_uniform_vec2(name, es_engine_shared::Vec2::new(v[0], v[1])).ok(); } + UniformValue::Vec3(v) => { backend.set_uniform_vec3(name, es_engine_shared::Vec3::new(v[0], v[1], v[2])).ok(); } + UniformValue::Vec4(v) => { backend.set_uniform_vec4(name, es_engine_shared::Vec4::new(v[0], v[1], v[2], v[3])).ok(); } + UniformValue::Int(v) => { backend.set_uniform_i32(name, *v).ok(); } + UniformValue::Mat3(v) => { backend.set_uniform_mat3(name, &es_engine_shared::Mat3::from_cols_array(v)).ok(); } + UniformValue::Mat4(v) => { backend.set_uniform_mat4(name, &es_engine_shared::Mat4::from_cols_array(v)).ok(); } + UniformValue::Sampler(v) => { backend.set_uniform_i32(name, *v).ok(); } + } + } + } } - // Switch texture if needed | 如需切换纹理 if batch_key.texture_id != current_texture_id { current_texture_id = batch_key.texture_id; - texture_manager.bind_texture(batch_key.texture_id, 0); + texture_manager.bind_texture_via_backend(backend, batch_key.texture_id, 0); } - // Flush this batch by index | 按索引刷新此批次 - self.sprite_batch.flush_batch_at(gl, batch_idx); + self.sprite_batch.flush_batch_at(backend, batch_idx); } - // Clear batch for next frame | 清空批处理以供下一帧使用 self.sprite_batch.clear(); - Ok(()) } - /// Get mutable reference to camera. - /// 获取相机的可变引用。 - #[inline] - pub fn camera_mut(&mut self) -> &mut Camera2D { - &mut self.camera + fn apply_scissor(&self, backend: &mut WebGL2Backend) { + if let Some(rect) = &self.scissor_rect { + backend.set_scissor(Some(ScissorRect { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + })); + } else { + backend.set_scissor(None); + } } - /// Get reference to camera. - /// 获取相机的引用。 #[inline] - pub fn camera(&self) -> &Camera2D { - &self.camera - } + pub fn camera_mut(&mut self) -> &mut Camera2D { &mut self.camera } + + #[inline] + pub fn camera(&self) -> &Camera2D { &self.camera } - /// Set clear color (RGBA, each component 0.0-1.0). - /// 设置清除颜色。 pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) { self.clear_color = [r, g, b, a]; } - /// Get clear color. - /// 获取清除颜色。 - pub fn get_clear_color(&self) -> [f32; 4] { - self.clear_color - } + pub fn get_clear_color(&self) -> [f32; 4] { self.clear_color } - /// Update camera viewport size. - /// 更新相机视口大小。 pub fn resize(&mut self, width: f32, height: f32) { self.camera.set_viewport(width, height); self.viewport_height = height; } - // ============= Scissor Test ============= - // ============= 裁剪测试 ============= - - /// Set scissor rect for clipping (screen coordinates, Y-down). - /// 设置裁剪矩形(屏幕坐标,Y 轴向下)。 - /// - /// Content outside this rect will be clipped. - /// 此矩形外的内容将被裁剪。 - /// - /// # Arguments | 参数 - /// * `x` - Left edge in screen coordinates | 屏幕坐标中的左边缘 - /// * `y` - Top edge in screen coordinates (Y-down) | 屏幕坐标中的上边缘(Y 向下) - /// * `width` - Rect width | 矩形宽度 - /// * `height` - Rect height | 矩形高度 pub fn set_scissor_rect(&mut self, x: f32, y: f32, width: f32, height: f32) { - self.scissor_rect = Some([x, y, width, height]); + self.scissor_rect = Some(ScissorRect { + x: x as i32, y: y as i32, + width: width as u32, height: height as u32, + }); } - /// Clear scissor rect (disable clipping). - /// 清除裁剪矩形(禁用裁剪)。 - pub fn clear_scissor_rect(&mut self) { - self.scissor_rect = None; + pub fn clear_scissor_rect(&mut self) { self.scissor_rect = None; } + + pub fn compile_shader(&mut self, backend: &mut WebGL2Backend, vertex: &str, fragment: &str) -> Result { + let handle = backend.compile_shader(vertex, fragment) + .map_err(|e| format!("{:?}", e))?; + let id = self.next_shader_id; + self.next_shader_id += 1; + self.custom_shaders.insert(id, handle); + Ok(id) } - /// Apply current scissor state to GL context. - /// 应用当前裁剪状态到 GL 上下文。 - fn apply_scissor(&self, gl: &WebGl2RenderingContext) { - if let Some([x, y, width, height]) = self.scissor_rect { - gl.enable(WebGl2RenderingContext::SCISSOR_TEST); - // WebGL scissor uses bottom-left origin with Y-up - // Convert from screen coordinates (top-left origin, Y-down) - // WebGL scissor 使用左下角原点,Y 轴向上 - // 从屏幕坐标转换(左上角原点,Y 轴向下) - let gl_y = self.viewport_height - y - height; - gl.scissor(x as i32, gl_y as i32, width as i32, height as i32); - } else { - gl.disable(WebGl2RenderingContext::SCISSOR_TEST); + pub fn compile_shader_with_id(&mut self, backend: &mut WebGL2Backend, id: u32, vertex: &str, fragment: &str) -> Result<(), String> { + let handle = backend.compile_shader(vertex, fragment) + .map_err(|e| format!("{:?}", e))?; + self.custom_shaders.insert(id, handle); + Ok(()) + } + + pub fn has_shader(&self, id: u32) -> bool { + id == 0 || self.custom_shaders.contains_key(&id) + } + + pub fn remove_shader(&mut self, id: u32) -> bool { + if id < 100 { return false; } + self.custom_shaders.remove(&id).is_some() + } + + pub fn register_material(&mut self, material: Material) -> u32 { + let id = self.materials.keys().max().unwrap_or(&0) + 1; + self.materials.insert(id, material); + id + } + + pub fn register_material_with_id(&mut self, id: u32, material: Material) { + self.materials.insert(id, material); + } + + pub fn get_material(&self, id: u32) -> Option<&Material> { self.materials.get(&id) } + pub fn get_material_mut(&mut self, id: u32) -> Option<&mut Material> { self.materials.get_mut(&id) } + pub fn has_material(&self, id: u32) -> bool { self.materials.contains_key(&id) } + pub fn remove_material(&mut self, id: u32) -> bool { self.materials.remove(&id).is_some() } + + pub fn set_material_float(&mut self, id: u32, name: &str, value: f32) -> bool { + if let Some(mat) = self.materials.get_mut(&id) { + mat.uniforms.set_float(name, value); + true + } else { false } + } + + pub fn set_material_vec4(&mut self, id: u32, name: &str, x: f32, y: f32, z: f32, w: f32) -> bool { + if let Some(mat) = self.materials.get_mut(&id) { + mat.uniforms.set_vec4(name, x, y, z, w); + true + } else { false } + } + + pub fn destroy(self, backend: &mut WebGL2Backend) { + self.sprite_batch.destroy(backend); + backend.destroy_shader(self.default_shader); + for (_, handle) in self.custom_shaders { + backend.destroy_shader(handle); } } - - // ============= Shader Management ============= - // ============= 着色器管理 ============= - - /// Compile and register a custom shader. - /// 编译并注册自定义着色器。 - /// - /// # Returns | 返回 - /// The shader ID for referencing this shader | 用于引用此着色器的ID - pub fn compile_shader( - &mut self, - gl: &WebGl2RenderingContext, - vertex_source: &str, - fragment_source: &str, - ) -> Result { - self.shader_manager.compile_shader(gl, vertex_source, fragment_source) - } - - /// Compile a shader with a specific ID. - /// 使用特定ID编译着色器。 - pub fn compile_shader_with_id( - &mut self, - gl: &WebGl2RenderingContext, - shader_id: u32, - vertex_source: &str, - fragment_source: &str, - ) -> Result<()> { - self.shader_manager.compile_shader_with_id(gl, shader_id, vertex_source, fragment_source) - } - - /// Check if a shader exists. - /// 检查着色器是否存在。 - pub fn has_shader(&self, shader_id: u32) -> bool { - self.shader_manager.has_shader(shader_id) - } - - /// Remove a shader. - /// 移除着色器。 - pub fn remove_shader(&mut self, shader_id: u32) -> bool { - self.shader_manager.remove_shader(shader_id) - } - - /// Get shader manager reference. - /// 获取着色器管理器引用。 - pub fn shader_manager(&self) -> &ShaderManager { - &self.shader_manager - } - - /// Get mutable shader manager reference. - /// 获取可变着色器管理器引用。 - pub fn shader_manager_mut(&mut self) -> &mut ShaderManager { - &mut self.shader_manager - } - - // ============= Material Management ============= - // ============= 材质管理 ============= - - /// Register a custom material. - /// 注册自定义材质。 - /// - /// # Returns | 返回 - /// The material ID for referencing this material | 用于引用此材质的ID - pub fn register_material(&mut self, material: super::material::Material) -> u32 { - self.material_manager.register_material(material) - } - - /// Register a material with a specific ID. - /// 使用特定ID注册材质。 - pub fn register_material_with_id(&mut self, material_id: u32, material: super::material::Material) { - self.material_manager.register_material_with_id(material_id, material); - } - - /// Get a material by ID. - /// 按ID获取材质。 - pub fn get_material(&self, material_id: u32) -> Option<&super::material::Material> { - self.material_manager.get_material(material_id) - } - - /// Get a mutable material by ID. - /// 按ID获取可变材质。 - pub fn get_material_mut(&mut self, material_id: u32) -> Option<&mut super::material::Material> { - self.material_manager.get_material_mut(material_id) - } - - /// Check if a material exists. - /// 检查材质是否存在。 - pub fn has_material(&self, material_id: u32) -> bool { - self.material_manager.has_material(material_id) - } - - /// Remove a material. - /// 移除材质。 - pub fn remove_material(&mut self, material_id: u32) -> bool { - self.material_manager.remove_material(material_id) - } - - /// Set a material's float uniform. - /// 设置材质的浮点uniform。 - pub fn set_material_float(&mut self, material_id: u32, name: &str, value: f32) -> bool { - self.material_manager.set_material_float(material_id, name, value) - } - - /// Set a material's vec4 uniform. - /// 设置材质的vec4 uniform。 - pub fn set_material_vec4(&mut self, material_id: u32, name: &str, x: f32, y: f32, z: f32, w: f32) -> bool { - self.material_manager.set_material_vec4(material_id, name, x, y, z, w) - } - - /// Get material manager reference. - /// 获取材质管理器引用。 - pub fn material_manager(&self) -> &MaterialManager { - &self.material_manager - } - - /// Get mutable material manager reference. - /// 获取可变材质管理器引用。 - pub fn material_manager_mut(&mut self) -> &mut MaterialManager { - &mut self.material_manager - } } diff --git a/packages/engine/src/renderer/texture/texture_manager.rs b/packages/engine/src/renderer/texture/texture_manager.rs index 4891f529..2b38e83e 100644 --- a/packages/engine/src/renderer/texture/texture_manager.rs +++ b/packages/engine/src/renderer/texture/texture_manager.rs @@ -9,6 +9,7 @@ use wasm_bindgen::JsCast; use web_sys::{HtmlImageElement, WebGl2RenderingContext, WebGlTexture}; use crate::core::error::{EngineError, Result}; +use crate::backend::WebGL2Backend; use super::Texture; /// 纹理加载状态 @@ -279,8 +280,6 @@ impl TextureManager { if let Some(texture) = self.textures.get(&id) { self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture.handle)); } else if let Some(default) = &self.default_texture { - // ID 0 is the default texture, no warning needed - // ID 0 是默认纹理,不需要警告 if id != 0 { log::warn!("Texture {} not found, using default | 未找到纹理 {},使用默认纹理", id, id); } @@ -290,6 +289,20 @@ impl TextureManager { } } + /// Bind texture via backend. + /// 通过后端绑定纹理。 + pub fn bind_texture_via_backend(&self, backend: &WebGL2Backend, id: u32, slot: u32) { + let texture = if let Some(tex) = self.textures.get(&id) { + Some(&tex.handle) + } else { + if id != 0 { + log::warn!("Texture {} not found, using default | 未找到纹理 {},使用默认纹理", id, id); + } + self.default_texture.as_ref() + }; + backend.bind_texture_raw(texture, slot); + } + /// Check if texture is loaded. /// 检查纹理是否已加载。 #[inline]