refactor(render): 抽象图形后端并迁移渲染器 (#313)
* 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 类型检查警告
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -90,3 +90,6 @@ docs/.vitepress/dist/
|
|||||||
# Tauri 捆绑输出
|
# Tauri 捆绑输出
|
||||||
**/src-tauri/target/release/bundle/
|
**/src-tauri/target/release/bundle/
|
||||||
**/src-tauri/target/debug/bundle/
|
**/src-tauri/target/debug/bundle/
|
||||||
|
|
||||||
|
# Rust 构建产物
|
||||||
|
**/engine-shared/target/
|
||||||
|
|||||||
@@ -1,363 +1,130 @@
|
|||||||
/**
|
/**
|
||||||
* 组件序列化器
|
* 组件序列化器
|
||||||
*
|
*
|
||||||
* 负责组件的序列化和反序列化操作
|
* Component serializer for ECS components.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component } from '../Component';
|
import { Component } from '../Component';
|
||||||
import { ComponentType } from '../Core/ComponentStorage';
|
import { ComponentType } from '../Core/ComponentStorage';
|
||||||
import { getComponentTypeName, isEntityRefProperty } from '../Decorators';
|
import { getComponentTypeName, isEntityRefProperty } from '../Decorators';
|
||||||
import {
|
import { getSerializationMetadata } from './SerializationDecorators';
|
||||||
getSerializationMetadata
|
import { ValueSerializer, SerializableValue } from './ValueSerializer';
|
||||||
} from './SerializationDecorators';
|
|
||||||
import type { Entity } from '../Entity';
|
import type { Entity } from '../Entity';
|
||||||
import type { SerializationContext, SerializedEntityRef } from './SerializationContext';
|
import type { SerializationContext, SerializedEntityRef } from './SerializationContext';
|
||||||
|
|
||||||
/**
|
export type { SerializableValue } from './ValueSerializer';
|
||||||
* 可序列化的值类型
|
|
||||||
*/
|
|
||||||
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 interface SerializedComponent {
|
export interface SerializedComponent {
|
||||||
/**
|
|
||||||
* 组件类型名称
|
|
||||||
*/
|
|
||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化版本
|
|
||||||
*/
|
|
||||||
version: number;
|
version: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件数据
|
|
||||||
*/
|
|
||||||
data: Record<string, SerializableValue>;
|
data: Record<string, SerializableValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件序列化器类
|
|
||||||
*/
|
|
||||||
export class ComponentSerializer {
|
export class ComponentSerializer {
|
||||||
/**
|
static serialize(component: Component): SerializedComponent | null {
|
||||||
* 序列化单个组件
|
|
||||||
*
|
|
||||||
* @param component 要序列化的组件实例
|
|
||||||
* @returns 序列化后的组件数据,如果组件不可序列化则返回null
|
|
||||||
*/
|
|
||||||
public static serialize(component: Component): SerializedComponent | null {
|
|
||||||
const metadata = getSerializationMetadata(component);
|
const metadata = getSerializationMetadata(component);
|
||||||
|
if (!metadata) return null;
|
||||||
if (!metadata) {
|
|
||||||
// 组件没有使用@Serializable装饰器,不可序列化
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentType = component.constructor as ComponentType;
|
const componentType = component.constructor as ComponentType;
|
||||||
const typeName = metadata.options.typeId || getComponentTypeName(componentType);
|
const typeName = metadata.options.typeId || getComponentTypeName(componentType);
|
||||||
const data: Record<string, SerializableValue> = {};
|
const data: Record<string, SerializableValue> = {};
|
||||||
|
|
||||||
// 序列化标记的字段
|
|
||||||
for (const [fieldName, options] of metadata.fields) {
|
for (const [fieldName, options] of metadata.fields) {
|
||||||
|
if (metadata.ignoredFields.has(fieldName)) continue;
|
||||||
|
|
||||||
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
|
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
|
||||||
const value = (component as unknown as Record<string | symbol, unknown>)[fieldName];
|
const value = (component as unknown as Record<string | symbol, unknown>)[fieldName];
|
||||||
|
|
||||||
// 跳过忽略的字段
|
|
||||||
if (metadata.ignoredFields.has(fieldName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let serializedValue: SerializableValue;
|
let serializedValue: SerializableValue;
|
||||||
|
|
||||||
// 检查是否为 EntityRef 属性
|
|
||||||
if (isEntityRefProperty(component, fieldKey)) {
|
if (isEntityRefProperty(component, fieldKey)) {
|
||||||
serializedValue = this.serializeEntityRef(value as Entity | null);
|
serializedValue = this.serializeEntityRef(value as Entity | null);
|
||||||
} else if (options.serializer) {
|
} else if (options.serializer) {
|
||||||
// 使用自定义序列化器
|
|
||||||
serializedValue = options.serializer(value);
|
serializedValue = options.serializer(value);
|
||||||
} else {
|
} else {
|
||||||
// 使用默认序列化
|
serializedValue = ValueSerializer.serialize(value);
|
||||||
serializedValue = this.serializeValue(value as SerializableValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用别名或原始字段名
|
data[options.alias || fieldKey] = serializedValue;
|
||||||
const key = options.alias || fieldKey;
|
|
||||||
data[key] = serializedValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { type: typeName, version: metadata.options.version, data };
|
||||||
type: typeName,
|
|
||||||
version: metadata.options.version,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static deserialize(
|
||||||
* 反序列化组件
|
|
||||||
*
|
|
||||||
* @param serializedData 序列化的组件数据
|
|
||||||
* @param componentRegistry 组件类型注册表 (类型名 -> 构造函数)
|
|
||||||
* @param context 序列化上下文(可选,用于解析 EntityRef)
|
|
||||||
* @returns 反序列化后的组件实例,如果失败则返回null
|
|
||||||
*/
|
|
||||||
public static deserialize(
|
|
||||||
serializedData: SerializedComponent,
|
serializedData: SerializedComponent,
|
||||||
componentRegistry: Map<string, ComponentType>,
|
componentRegistry: Map<string, ComponentType>,
|
||||||
context?: SerializationContext
|
context?: SerializationContext
|
||||||
): Component | null {
|
): Component | null {
|
||||||
const componentClass = componentRegistry.get(serializedData.type);
|
const componentClass = componentRegistry.get(serializedData.type);
|
||||||
|
|
||||||
if (!componentClass) {
|
if (!componentClass) {
|
||||||
console.warn(`未找到组件类型: ${serializedData.type}`);
|
console.warn(`Component type not found: ${serializedData.type}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = getSerializationMetadata(componentClass);
|
const metadata = getSerializationMetadata(componentClass);
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
console.warn(`组件 ${serializedData.type} 不可序列化`);
|
console.warn(`Component ${serializedData.type} is not serializable`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建组件实例
|
|
||||||
const component = new componentClass();
|
const component = new componentClass();
|
||||||
|
|
||||||
// 反序列化字段
|
|
||||||
for (const [fieldName, options] of metadata.fields) {
|
for (const [fieldName, options] of metadata.fields) {
|
||||||
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
|
const fieldKey = typeof fieldName === 'symbol' ? fieldName.toString() : fieldName;
|
||||||
const key = options.alias || fieldKey;
|
const key = options.alias || fieldKey;
|
||||||
const serializedValue = serializedData.data[key];
|
const serializedValue = serializedData.data[key];
|
||||||
|
|
||||||
if (serializedValue === undefined) {
|
if (serializedValue === undefined) continue;
|
||||||
continue; // 字段不存在于序列化数据中
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否为序列化的 EntityRef
|
|
||||||
if (this.isSerializedEntityRef(serializedValue)) {
|
if (this.isSerializedEntityRef(serializedValue)) {
|
||||||
// EntityRef 需要延迟解析
|
|
||||||
if (context) {
|
if (context) {
|
||||||
const ref = serializedValue.__entityRef;
|
const ref = serializedValue.__entityRef;
|
||||||
context.registerPendingRef(component, fieldKey, ref.id, ref.guid);
|
context.registerPendingRef(component, fieldKey, ref.id, ref.guid);
|
||||||
}
|
}
|
||||||
// 暂时设为 null,后续由 context.resolveAllReferences() 填充
|
|
||||||
(component as unknown as Record<string | symbol, unknown>)[fieldName] = null;
|
(component as unknown as Record<string | symbol, unknown>)[fieldName] = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用自定义反序列化器或默认反序列化
|
|
||||||
const value = options.deserializer
|
const value = options.deserializer
|
||||||
? options.deserializer(serializedValue)
|
? options.deserializer(serializedValue)
|
||||||
: this.deserializeValue(serializedValue);
|
: ValueSerializer.deserialize(serializedValue);
|
||||||
|
|
||||||
(component as unknown as Record<string | symbol, SerializableValue>)[fieldName] = value;
|
(component as unknown as Record<string | symbol, unknown>)[fieldName] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static serializeComponents(components: Component[]): SerializedComponent[] {
|
||||||
* 批量序列化组件
|
return components
|
||||||
*
|
.map(c => this.serialize(c))
|
||||||
* @param components 组件数组
|
.filter((s): s is SerializedComponent => s !== null);
|
||||||
* @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 deserializeComponents(
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量反序列化组件
|
|
||||||
*
|
|
||||||
* @param serializedComponents 序列化的组件数据数组
|
|
||||||
* @param componentRegistry 组件类型注册表
|
|
||||||
* @param context 序列化上下文(可选,用于解析 EntityRef)
|
|
||||||
* @returns 反序列化后的组件数组
|
|
||||||
*/
|
|
||||||
public static deserializeComponents(
|
|
||||||
serializedComponents: SerializedComponent[],
|
serializedComponents: SerializedComponent[],
|
||||||
componentRegistry: Map<string, ComponentType>,
|
componentRegistry: Map<string, ComponentType>,
|
||||||
context?: SerializationContext
|
context?: SerializationContext
|
||||||
): Component[] {
|
): Component[] {
|
||||||
const result: Component[] = [];
|
return serializedComponents
|
||||||
|
.map(s => this.deserialize(s, componentRegistry, context))
|
||||||
for (const serialized of serializedComponents) {
|
.filter((c): c is Component => c !== null);
|
||||||
const component = this.deserialize(serialized, componentRegistry, context);
|
|
||||||
if (component) {
|
|
||||||
result.push(component);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
static validateVersion(serializedData: SerializedComponent, expectedVersion: number): boolean {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认值序列化
|
|
||||||
*
|
|
||||||
* 处理基本类型、数组、对象等的序列化
|
|
||||||
*/
|
|
||||||
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<string, SerializableValue> = {};
|
|
||||||
const obj = value as Record<string, SerializableValue>;
|
|
||||||
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<string, SerializableValue> = {};
|
|
||||||
const obj = value as Record<string, SerializableValue>;
|
|
||||||
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 {
|
|
||||||
return serializedData.version === expectedVersion;
|
return serializedData.version === expectedVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static getSerializationInfo(component: Component | ComponentType): {
|
||||||
* 获取组件的序列化信息
|
|
||||||
*
|
|
||||||
* @param component 组件实例或组件类
|
|
||||||
* @returns 序列化信息对象,包含类型名、版本、可序列化字段列表
|
|
||||||
*/
|
|
||||||
public static getSerializationInfo(component: Component | ComponentType): {
|
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
fields: string[];
|
fields: string[];
|
||||||
ignoredFields: string[];
|
ignoredFields: string[];
|
||||||
isSerializable: boolean;
|
isSerializable: boolean;
|
||||||
} | null {
|
} {
|
||||||
const metadata = getSerializationMetadata(component);
|
const metadata = getSerializationMetadata(component);
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return {
|
return { type: 'unknown', version: 0, fields: [], ignoredFields: [], isSerializable: false };
|
||||||
type: 'unknown',
|
|
||||||
version: 0,
|
|
||||||
fields: [],
|
|
||||||
ignoredFields: [],
|
|
||||||
isSerializable: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentType = typeof component === 'function'
|
const componentType = typeof component === 'function'
|
||||||
@@ -367,50 +134,18 @@ export class ComponentSerializer {
|
|||||||
return {
|
return {
|
||||||
type: metadata.options.typeId || getComponentTypeName(componentType),
|
type: metadata.options.typeId || getComponentTypeName(componentType),
|
||||||
version: metadata.options.version,
|
version: metadata.options.version,
|
||||||
fields: Array.from(metadata.fields.keys()).map((k) =>
|
fields: Array.from(metadata.fields.keys()).map(k => typeof k === 'symbol' ? k.toString() : k),
|
||||||
typeof k === 'symbol' ? k.toString() : k
|
ignoredFields: Array.from(metadata.ignoredFields).map(k => typeof k === 'symbol' ? k.toString() : k),
|
||||||
),
|
|
||||||
ignoredFields: Array.from(metadata.ignoredFields).map((k) =>
|
|
||||||
typeof k === 'symbol' ? k.toString() : k
|
|
||||||
),
|
|
||||||
isSerializable: true
|
isSerializable: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static serializeEntityRef(entity: Entity | null): SerializableValue {
|
||||||
* 序列化 Entity 引用
|
if (!entity) return null;
|
||||||
*
|
return { __entityRef: { id: entity.id, guid: entity.persistentId } };
|
||||||
* 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 {
|
static isSerializedEntityRef(value: unknown): value is { __entityRef: SerializedEntityRef } {
|
||||||
__entityRef: {
|
return typeof value === 'object' && value !== null && '__entityRef' in value;
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { BinarySerializer } from '../../Utils/BinarySerializer';
|
|||||||
import { HierarchySystem } from '../Systems/HierarchySystem';
|
import { HierarchySystem } from '../Systems/HierarchySystem';
|
||||||
import { HierarchyComponent } from '../Components/HierarchyComponent';
|
import { HierarchyComponent } from '../Components/HierarchyComponent';
|
||||||
import { SerializationContext } from './SerializationContext';
|
import { SerializationContext } from './SerializationContext';
|
||||||
|
import { ValueSerializer, SerializableValue } from './ValueSerializer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 场景序列化格式
|
* 场景序列化格式
|
||||||
@@ -387,131 +388,21 @@ export class SceneSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static serializeSceneData(sceneData: Map<string, unknown>): Record<string, unknown> {
|
||||||
* 序列化场景自定义数据
|
const result: Record<string, unknown> = {};
|
||||||
*
|
|
||||||
* 将 Map<string, any> 转换为普通对象
|
|
||||||
*/
|
|
||||||
private static serializeSceneData(sceneData: Map<string, any>): Record<string, any> {
|
|
||||||
const result: Record<string, any> = {};
|
|
||||||
|
|
||||||
for (const [key, value] of sceneData) {
|
for (const [key, value] of sceneData) {
|
||||||
result[key] = this.serializeValue(value);
|
result[key] = ValueSerializer.serialize(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static deserializeSceneData(data: Record<string, unknown>, targetMap: Map<string, unknown>): void {
|
||||||
* 反序列化场景自定义数据
|
|
||||||
*
|
|
||||||
* 将普通对象还原为 Map<string, any>
|
|
||||||
*/
|
|
||||||
private static deserializeSceneData(
|
|
||||||
data: Record<string, any>,
|
|
||||||
targetMap: Map<string, any>
|
|
||||||
): void {
|
|
||||||
targetMap.clear();
|
targetMap.clear();
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
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<string, any> = {};
|
|
||||||
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<string, any> = {};
|
|
||||||
for (const key in value) {
|
|
||||||
if (value.hasOwnProperty(key)) {
|
|
||||||
result[key] = this.deserializeValue(value[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过滤要序列化的实体和组件
|
* 过滤要序列化的实体和组件
|
||||||
*/
|
*/
|
||||||
|
|||||||
113
packages/core/src/ECS/Serialization/ValueSerializer.ts
Normal file
113
packages/core/src/ECS/Serialization/ValueSerializer.ts
Normal file
@@ -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<T> = (value: T, serialize: (v: unknown) => SerializableValue) => SerializableValue;
|
||||||
|
type Deserializer<T> = (data: { __type: string; value: unknown }) => T;
|
||||||
|
|
||||||
|
interface TypeDef<T = unknown> {
|
||||||
|
check: (value: unknown) => value is T;
|
||||||
|
serialize: Serializer<T>;
|
||||||
|
deserialize: Deserializer<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = new Map<string, TypeDef>();
|
||||||
|
|
||||||
|
function registerType<T>(name: string, def: TypeDef<T>): void {
|
||||||
|
types.set(name, def as TypeDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内置类型
|
||||||
|
registerType<Date>('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<unknown, unknown>>('Map', {
|
||||||
|
check: (v): v is Map<unknown, unknown> => 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<unknown>>('Set', {
|
||||||
|
check: (v): v is Set<unknown> => 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<object>()): 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<string, SerializableValue> = {};
|
||||||
|
for (const k of Object.keys(value as object)) {
|
||||||
|
result[k] = serialize((value as Record<string, unknown>)[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<string, unknown> = {};
|
||||||
|
for (const k of Object.keys(value)) {
|
||||||
|
result[k] = deserialize((value as Record<string, SerializableValue>)[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 };
|
||||||
@@ -24,6 +24,10 @@ export type {
|
|||||||
SerializationMetadata
|
SerializationMetadata
|
||||||
} from './SerializationDecorators';
|
} from './SerializationDecorators';
|
||||||
|
|
||||||
|
// 值序列化器
|
||||||
|
export { ValueSerializer } from './ValueSerializer';
|
||||||
|
export type { SerializableValue, TypeHandler, TypedValue } from './ValueSerializer';
|
||||||
|
|
||||||
// 组件序列化器
|
// 组件序列化器
|
||||||
export { ComponentSerializer } from './ComponentSerializer';
|
export { ComponentSerializer } from './ComponentSerializer';
|
||||||
export type { SerializedComponent } from './ComponentSerializer';
|
export type { SerializedComponent } from './ComponentSerializer';
|
||||||
|
|||||||
127
packages/engine-shared/Cargo.lock
generated
Normal file
127
packages/engine-shared/Cargo.lock
generated
Normal file
@@ -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"
|
||||||
33
packages/engine-shared/Cargo.toml
Normal file
33
packages/engine-shared/Cargo.toml
Normal file
@@ -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]
|
||||||
7
packages/engine-shared/src/batch/mod.rs
Normal file
7
packages/engine-shared/src/batch/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! 批处理数据结构
|
||||||
|
//!
|
||||||
|
//! Batch data structures.
|
||||||
|
|
||||||
|
mod sprite_data;
|
||||||
|
|
||||||
|
pub use sprite_data::*;
|
||||||
417
packages/engine-shared/src/batch/sprite_data.rs
Normal file
417
packages/engine-shared/src/batch/sprite_data.rs
Normal file
@@ -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<SpriteVertex>,
|
||||||
|
|
||||||
|
/// 索引数据 | Index data
|
||||||
|
indices: Vec<u16>,
|
||||||
|
|
||||||
|
/// 批次列表:(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<BatchKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<F>(
|
||||||
|
&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());
|
||||||
|
}
|
||||||
|
}
|
||||||
421
packages/engine-shared/src/camera.rs
Normal file
421
packages/engine-shared/src/camera.rs
Normal file
@@ -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<Vec2> = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
packages/engine-shared/src/lib.rs
Normal file
40
packages/engine-shared/src/lib.rs
Normal file
@@ -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};
|
||||||
519
packages/engine-shared/src/traits/backend.rs
Normal file
519
packages/engine-shared/src/traits/backend.rs
Normal file
@@ -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<T> = Result<T, GraphicsError>;
|
||||||
|
|
||||||
|
// ==================== 缓冲区用途 | 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<BufferHandle>;
|
||||||
|
|
||||||
|
/// 创建指定大小的顶点缓冲区(预分配)
|
||||||
|
///
|
||||||
|
/// Create vertex buffer with specified size (pre-allocate).
|
||||||
|
fn create_vertex_buffer_sized(
|
||||||
|
&mut self,
|
||||||
|
size: usize,
|
||||||
|
usage: BufferUsage,
|
||||||
|
) -> GraphicsResult<BufferHandle>;
|
||||||
|
|
||||||
|
/// 创建索引缓冲区
|
||||||
|
///
|
||||||
|
/// Create index buffer.
|
||||||
|
fn create_index_buffer(
|
||||||
|
&mut self,
|
||||||
|
data: &[u16],
|
||||||
|
usage: BufferUsage,
|
||||||
|
) -> GraphicsResult<BufferHandle>;
|
||||||
|
|
||||||
|
/// 创建索引缓冲区(u32)
|
||||||
|
///
|
||||||
|
/// Create index buffer (u32).
|
||||||
|
fn create_index_buffer_u32(
|
||||||
|
&mut self,
|
||||||
|
data: &[u32],
|
||||||
|
usage: BufferUsage,
|
||||||
|
) -> GraphicsResult<BufferHandle>;
|
||||||
|
|
||||||
|
/// 更新缓冲区数据
|
||||||
|
///
|
||||||
|
/// 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<BufferHandle>,
|
||||||
|
layout: &VertexLayout,
|
||||||
|
) -> GraphicsResult<VertexArrayHandle>;
|
||||||
|
|
||||||
|
/// 销毁顶点数组对象
|
||||||
|
///
|
||||||
|
/// 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<ShaderHandle>;
|
||||||
|
|
||||||
|
/// 销毁着色器
|
||||||
|
///
|
||||||
|
/// 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<TextureHandle>;
|
||||||
|
|
||||||
|
/// 创建空白纹理(用于动态图集)
|
||||||
|
///
|
||||||
|
/// Create blank texture (for dynamic atlas).
|
||||||
|
fn create_blank_texture(&mut self, width: u32, height: u32) -> GraphicsResult<TextureHandle>;
|
||||||
|
|
||||||
|
/// 上传纹理数据
|
||||||
|
///
|
||||||
|
/// 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<ScissorRect>);
|
||||||
|
|
||||||
|
// ==================== 绘制命令 | 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<TextureHandle>,
|
||||||
|
) -> GraphicsResult<FramebufferHandle>;
|
||||||
|
|
||||||
|
/// 销毁帧缓冲区
|
||||||
|
///
|
||||||
|
/// Destroy framebuffer.
|
||||||
|
fn destroy_framebuffer(&mut self, handle: FramebufferHandle);
|
||||||
|
|
||||||
|
/// 绑定帧缓冲区
|
||||||
|
///
|
||||||
|
/// Bind framebuffer.
|
||||||
|
fn bind_framebuffer(&mut self, handle: Option<FramebufferHandle>) -> 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<()>;
|
||||||
|
}
|
||||||
7
packages/engine-shared/src/traits/mod.rs
Normal file
7
packages/engine-shared/src/traits/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! 图形后端 trait 定义
|
||||||
|
//!
|
||||||
|
//! Graphics backend trait definitions.
|
||||||
|
|
||||||
|
pub mod backend;
|
||||||
|
pub mod platform;
|
||||||
|
pub mod renderer;
|
||||||
441
packages/engine-shared/src/traits/platform.rs
Normal file
441
packages/engine-shared/src/traits/platform.rs
Normal file
@@ -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<String>,
|
||||||
|
|
||||||
|
/// 初始宽度
|
||||||
|
///
|
||||||
|
/// 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<String>) -> 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<T> = Result<T, AssetError>;
|
||||||
|
|
||||||
|
/// 资产加载器 trait
|
||||||
|
///
|
||||||
|
/// Asset loader trait.
|
||||||
|
pub trait AssetLoader {
|
||||||
|
/// 加载二进制数据
|
||||||
|
///
|
||||||
|
/// Load binary data.
|
||||||
|
fn load_bytes(&self, path: &str) -> impl Future<Output = AssetResult<Vec<u8>>> + Send;
|
||||||
|
|
||||||
|
/// 加载文本
|
||||||
|
///
|
||||||
|
/// Load text.
|
||||||
|
fn load_text(&self, path: &str) -> impl Future<Output = AssetResult<String>> + Send;
|
||||||
|
|
||||||
|
/// 加载图片(返回 RGBA 数据)
|
||||||
|
///
|
||||||
|
/// Load image (returns RGBA data).
|
||||||
|
fn load_image(&self, path: &str) -> impl Future<Output = AssetResult<ImageData>> + 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<Self::Backend>;
|
||||||
|
|
||||||
|
/// 获取资产加载器
|
||||||
|
///
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
293
packages/engine-shared/src/traits/renderer.rs
Normal file
293
packages/engine-shared/src/traits/renderer.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
333
packages/engine-shared/src/types/blend.rs
Normal file
333
packages/engine-shared/src/types/blend.rs
Normal file
@@ -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<ScissorRect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
400
packages/engine-shared/src/types/handle.rs
Normal file
400
packages/engine-shared/src/types/handle.rs
Normal file
@@ -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<T> {
|
||||||
|
/// 内部 ID | Internal ID
|
||||||
|
id: u32,
|
||||||
|
/// 代数(用于检测过期句柄) | Generation for stale handle detection
|
||||||
|
generation: u32,
|
||||||
|
/// 类型标记 | Type marker
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动实现 trait,因为 PhantomData 的存在
|
||||||
|
impl<T> Clone for Handle<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for Handle<T> {}
|
||||||
|
|
||||||
|
impl<T> PartialEq for Handle<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id && self.generation == other.generation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Eq for Handle<T> {}
|
||||||
|
|
||||||
|
impl<T> Hash for Handle<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
self.generation.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Handle<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Handle")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("generation", &self.generation)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Handle<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Handle<T> {
|
||||||
|
/// 创建新句柄
|
||||||
|
///
|
||||||
|
/// 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<BufferMarker>;
|
||||||
|
|
||||||
|
/// 顶点数组对象句柄 | Vertex array object handle
|
||||||
|
pub type VertexArrayHandle = Handle<VertexArrayMarker>;
|
||||||
|
|
||||||
|
/// 着色器程序句柄 | Shader program handle
|
||||||
|
pub type ShaderHandle = Handle<ShaderMarker>;
|
||||||
|
|
||||||
|
/// 纹理句柄 | Texture handle
|
||||||
|
pub type TextureHandle = Handle<TextureMarker>;
|
||||||
|
|
||||||
|
/// 材质句柄 | Material handle
|
||||||
|
pub type MaterialHandle = Handle<MaterialMarker>;
|
||||||
|
|
||||||
|
/// 帧缓冲区句柄 | Framebuffer handle
|
||||||
|
pub type FramebufferHandle = Handle<FramebufferMarker>;
|
||||||
|
|
||||||
|
/// 渲染缓冲区句柄 | Renderbuffer handle
|
||||||
|
pub type RenderbufferHandle = Handle<RenderbufferMarker>;
|
||||||
|
|
||||||
|
/// 采样器句柄 | Sampler handle
|
||||||
|
pub type SamplerHandle = Handle<SamplerMarker>;
|
||||||
|
|
||||||
|
// ==================== 句柄映射 | Handle Map ====================
|
||||||
|
|
||||||
|
/// 带代数的句柄映射
|
||||||
|
///
|
||||||
|
/// 用于管理资源的分配和释放,支持句柄重用和过期检测。
|
||||||
|
///
|
||||||
|
/// Handle map with generation tracking.
|
||||||
|
/// Used for managing resource allocation and deallocation,
|
||||||
|
/// supports handle reuse and stale detection.
|
||||||
|
pub struct HandleMap<T> {
|
||||||
|
/// 存储项:(资源, 代数) | Items: (resource, generation)
|
||||||
|
items: Vec<Option<(T, u32)>>,
|
||||||
|
/// 空闲列表 | Free list
|
||||||
|
free_list: Vec<u32>,
|
||||||
|
/// 下一个代数 | Next generation
|
||||||
|
next_generation: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for HandleMap<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HandleMap<T> {
|
||||||
|
/// 创建新的句柄映射
|
||||||
|
///
|
||||||
|
/// 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<M>(&mut self, item: T) -> Handle<M> {
|
||||||
|
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<M>(&self, handle: Handle<M>) -> 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<M>(&mut self, handle: Handle<M>) -> 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<M>(&mut self, handle: Handle<M>) -> Option<T> {
|
||||||
|
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<M>(&self, handle: Handle<M>) -> 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<Item = &T> {
|
||||||
|
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<Item = &mut T> {
|
||||||
|
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<String> = 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()); // 但代数不同
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/engine-shared/src/types/mod.rs
Normal file
9
packages/engine-shared/src/types/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//! 共享类型定义
|
||||||
|
//!
|
||||||
|
//! Shared type definitions.
|
||||||
|
|
||||||
|
pub mod handle;
|
||||||
|
pub mod vertex;
|
||||||
|
pub mod blend;
|
||||||
|
pub mod uniform;
|
||||||
|
pub mod texture;
|
||||||
383
packages/engine-shared/src/types/texture.rs
Normal file
383
packages/engine-shared/src/types/texture.rs
Normal file
@@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>) -> 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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageData {
|
||||||
|
/// 创建新的图片数据
|
||||||
|
///
|
||||||
|
/// Create new image data.
|
||||||
|
pub fn new(width: u32, height: u32, data: Vec<u8>) -> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
307
packages/engine-shared/src/types/uniform.rs
Normal file
307
packages/engine-shared/src/types/uniform.rs
Normal file
@@ -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<f32> for UniformValue {
|
||||||
|
fn from(v: f32) -> Self {
|
||||||
|
Self::Float(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec2> for UniformValue {
|
||||||
|
fn from(v: Vec2) -> Self {
|
||||||
|
Self::Float2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for UniformValue {
|
||||||
|
fn from(v: Vec3) -> Self {
|
||||||
|
Self::Float3(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec4> for UniformValue {
|
||||||
|
fn from(v: Vec4) -> Self {
|
||||||
|
Self::Float4(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> 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<u32> for UniformValue {
|
||||||
|
fn from(v: u32) -> Self {
|
||||||
|
Self::UInt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mat3> for UniformValue {
|
||||||
|
fn from(v: Mat3) -> Self {
|
||||||
|
Self::Mat3(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mat4> 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<String>, value: impl Into<UniformValue>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
value: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uniform 集合
|
||||||
|
///
|
||||||
|
/// Uniform set.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct UniformSet {
|
||||||
|
/// Uniform 列表 | Uniform list
|
||||||
|
bindings: Vec<UniformBinding>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>, value: impl Into<UniformValue>) {
|
||||||
|
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<UniformValue> {
|
||||||
|
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<Item = &UniformBinding> {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
352
packages/engine-shared/src/types/vertex.rs
Normal file
352
packages/engine-shared/src/types/vertex.rs
Normal file
@@ -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<VertexAttribute>,
|
||||||
|
|
||||||
|
/// 步幅(单个顶点字节大小)
|
||||||
|
///
|
||||||
|
/// Stride (bytes per vertex).
|
||||||
|
pub stride: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VertexLayout {
|
||||||
|
/// 创建新的顶点布局
|
||||||
|
///
|
||||||
|
/// Create new vertex layout.
|
||||||
|
pub fn new(attributes: Vec<VertexAttribute>, stride: usize) -> Self {
|
||||||
|
Self { attributes, stride }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从属性列表自动计算步幅
|
||||||
|
///
|
||||||
|
/// Auto-calculate stride from attribute list.
|
||||||
|
pub fn from_attributes(attributes: Vec<VertexAttribute>) -> 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::<SpriteVertex>(), 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_2d_vertex_size() {
|
||||||
|
assert_eq!(mem::size_of::<Simple2DVertex>(), 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_textured_2d_vertex_size() {
|
||||||
|
assert_eq!(mem::size_of::<Textured2DVertex>(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vertex_layout_stride() {
|
||||||
|
let layout = VertexLayout::sprite();
|
||||||
|
assert_eq!(layout.stride, 36);
|
||||||
|
assert_eq!(layout.attributes.len(), 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Shared types and traits | 共享类型和trait
|
||||||
|
es-engine-shared = { path = "../engine-shared" }
|
||||||
|
|
||||||
# WASM bindings | WASM绑定
|
# WASM bindings | WASM绑定
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
|
|||||||
10
packages/engine/src/backend/mod.rs
Normal file
10
packages/engine/src/backend/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//! 图形后端实现
|
||||||
|
//!
|
||||||
|
//! Graphics backend implementations.
|
||||||
|
//!
|
||||||
|
//! 本模块提供 `GraphicsBackend` trait 的具体实现。
|
||||||
|
//! This module provides concrete implementations of the `GraphicsBackend` trait.
|
||||||
|
|
||||||
|
mod webgl2;
|
||||||
|
|
||||||
|
pub use webgl2::WebGL2Backend;
|
||||||
962
packages/engine/src/backend/webgl2.rs
Normal file
962
packages/engine/src/backend/webgl2.rs
Normal file
@@ -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<String, Option<WebGlUniformLocation>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 纹理数据
|
||||||
|
///
|
||||||
|
/// 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<IndexType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 索引类型
|
||||||
|
///
|
||||||
|
/// 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<HtmlCanvasElement>,
|
||||||
|
|
||||||
|
/// 当前宽度 | Current width
|
||||||
|
width: u32,
|
||||||
|
|
||||||
|
/// 当前高度 | Current height
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
// ===== 资源管理 | Resource Management =====
|
||||||
|
|
||||||
|
/// 缓冲区映射 | Buffer map
|
||||||
|
buffers: HandleMap<WebGlBuffer>,
|
||||||
|
|
||||||
|
/// 顶点数组对象映射 | VAO map
|
||||||
|
vertex_arrays: HandleMap<VertexArrayData>,
|
||||||
|
|
||||||
|
/// 着色器映射 | Shader map
|
||||||
|
shaders: HandleMap<ShaderData>,
|
||||||
|
|
||||||
|
/// 纹理映射 | Texture map
|
||||||
|
textures: HandleMap<TextureData>,
|
||||||
|
|
||||||
|
// ===== 当前状态 | Current State =====
|
||||||
|
|
||||||
|
/// 当前绑定的着色器 | Currently bound shader
|
||||||
|
current_shader: Option<ShaderHandle>,
|
||||||
|
|
||||||
|
/// 当前渲染状态 | 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<Self> {
|
||||||
|
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::<HtmlCanvasElement>()
|
||||||
|
.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::<GL>()
|
||||||
|
.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<Self> {
|
||||||
|
let gl = gl_context
|
||||||
|
.dyn_into::<GL>()
|
||||||
|
.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<WebGlUniformLocation> {
|
||||||
|
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<WebGlShader> {
|
||||||
|
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<BufferHandle> {
|
||||||
|
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<BufferHandle> {
|
||||||
|
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<BufferHandle> {
|
||||||
|
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<BufferHandle> {
|
||||||
|
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<BufferHandle>,
|
||||||
|
layout: &VertexLayout,
|
||||||
|
) -> GraphicsResult<VertexArrayHandle> {
|
||||||
|
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<ShaderHandle> {
|
||||||
|
// 编译顶点着色器
|
||||||
|
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<TextureHandle> {
|
||||||
|
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<TextureHandle> {
|
||||||
|
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<ScissorRect>) {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,11 @@ use wasm_bindgen::prelude::*;
|
|||||||
|
|
||||||
use super::context::WebGLContext;
|
use super::context::WebGLContext;
|
||||||
use super::error::Result;
|
use super::error::Result;
|
||||||
|
use crate::backend::WebGL2Backend;
|
||||||
use crate::input::InputManager;
|
use crate::input::InputManager;
|
||||||
use crate::renderer::{Renderer2D, GridRenderer, GizmoRenderer, TransformMode, ViewportManager};
|
use crate::renderer::{Renderer2D, GridRenderer, GizmoRenderer, TransformMode, ViewportManager};
|
||||||
use crate::resource::TextureManager;
|
use crate::resource::TextureManager;
|
||||||
|
use es_engine_shared::traits::backend::GraphicsBackend;
|
||||||
|
|
||||||
/// Engine configuration options.
|
/// Engine configuration options.
|
||||||
/// 引擎配置选项。
|
/// 引擎配置选项。
|
||||||
@@ -41,6 +43,15 @@ pub struct Engine {
|
|||||||
/// WebGL上下文。
|
/// WebGL上下文。
|
||||||
context: WebGLContext,
|
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.
|
||||||
/// 2D渲染器。
|
/// 2D渲染器。
|
||||||
renderer: Renderer2D,
|
renderer: Renderer2D,
|
||||||
@@ -104,17 +115,34 @@ impl Engine {
|
|||||||
context.set_viewport();
|
context.set_viewport();
|
||||||
context.enable_blend();
|
context.enable_blend();
|
||||||
|
|
||||||
// Create subsystems | 创建子系统
|
// Create graphics backend abstraction | 创建图形后端抽象
|
||||||
let renderer = Renderer2D::new(context.gl(), config.max_sprites)?;
|
let backend = WebGL2Backend::from_canvas(canvas_id)
|
||||||
let grid_renderer = GridRenderer::new(context.gl())?;
|
.map_err(|e| crate::core::error::EngineError::WebGLError(
|
||||||
let gizmo_renderer = GizmoRenderer::new(context.gl())?;
|
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 texture_manager = TextureManager::new(context.gl().clone());
|
||||||
let input_manager = InputManager::new();
|
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 | 引擎创建成功");
|
log::info!("Engine created successfully | 引擎创建成功");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
context,
|
context,
|
||||||
|
backend,
|
||||||
renderer,
|
renderer,
|
||||||
grid_renderer,
|
grid_renderer,
|
||||||
gizmo_renderer,
|
gizmo_renderer,
|
||||||
@@ -124,7 +152,7 @@ impl Engine {
|
|||||||
show_grid: true,
|
show_grid: true,
|
||||||
viewport_manager: ViewportManager::new(),
|
viewport_manager: ViewportManager::new(),
|
||||||
show_gizmos: true,
|
show_gizmos: true,
|
||||||
is_editor: true, // 默认为编辑器模式 | Default to editor mode
|
is_editor: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,21 +167,39 @@ impl Engine {
|
|||||||
height: u32,
|
height: u32,
|
||||||
config: EngineConfig,
|
config: EngineConfig,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let context = WebGLContext::from_external(gl_context, width, height)?;
|
let context = WebGLContext::from_external(gl_context.clone(), width, height)?;
|
||||||
|
|
||||||
context.set_viewport();
|
context.set_viewport();
|
||||||
context.enable_blend();
|
context.enable_blend();
|
||||||
|
|
||||||
let renderer = Renderer2D::new(context.gl(), config.max_sprites)?;
|
// Create graphics backend from external context | 从外部上下文创建图形后端
|
||||||
let grid_renderer = GridRenderer::new(context.gl())?;
|
let backend = WebGL2Backend::from_external(gl_context, width, height)
|
||||||
let gizmo_renderer = GizmoRenderer::new(context.gl())?;
|
.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 texture_manager = TextureManager::new(context.gl().clone());
|
||||||
let input_manager = InputManager::new();
|
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 | 从外部上下文创建引擎");
|
log::info!("Engine created from external context | 从外部上下文创建引擎");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
context,
|
context,
|
||||||
|
backend,
|
||||||
renderer,
|
renderer,
|
||||||
grid_renderer,
|
grid_renderer,
|
||||||
gizmo_renderer,
|
gizmo_renderer,
|
||||||
@@ -163,7 +209,7 @@ impl Engine {
|
|||||||
show_grid: true,
|
show_grid: true,
|
||||||
viewport_manager: ViewportManager::new(),
|
viewport_manager: ViewportManager::new(),
|
||||||
show_gizmos: true,
|
show_gizmos: true,
|
||||||
is_editor: true, // 默认为编辑器模式 | Default to editor mode
|
is_editor: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +233,50 @@ impl Engine {
|
|||||||
self.context.height()
|
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.
|
/// Submit sprite batch data for rendering.
|
||||||
/// 提交精灵批次数据进行渲染。
|
/// 提交精灵批次数据进行渲染。
|
||||||
pub fn submit_sprite_batch(
|
pub fn submit_sprite_batch(
|
||||||
@@ -197,41 +287,28 @@ impl Engine {
|
|||||||
colors: &[u32],
|
colors: &[u32],
|
||||||
material_ids: &[u32],
|
material_ids: &[u32],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.renderer.submit_batch(
|
self.renderer.submit_batch(transforms, texture_ids, uvs, colors, material_ids)
|
||||||
transforms,
|
.map_err(|e| crate::core::error::EngineError::WebGLError(e))
|
||||||
texture_ids,
|
|
||||||
uvs,
|
|
||||||
colors,
|
|
||||||
material_ids,
|
|
||||||
&self.texture_manager,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the current frame.
|
|
||||||
/// 渲染当前帧。
|
|
||||||
pub fn render(&mut self) -> Result<()> {
|
pub fn render(&mut self) -> Result<()> {
|
||||||
// Clear background with clear color
|
|
||||||
let [r, g, b, a] = self.renderer.get_clear_color();
|
let [r, g, b, a] = self.renderer.get_clear_color();
|
||||||
self.context.clear(r, g, b, a);
|
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 {
|
if self.is_editor && self.show_grid {
|
||||||
self.grid_renderer.render(self.context.gl(), self.renderer.camera());
|
self.grid_renderer.render(&mut self.backend, &camera);
|
||||||
self.grid_renderer.render_axes(self.context.gl(), self.renderer.camera());
|
self.grid_renderer.render_axes(&mut self.backend, &camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render sprites
|
self.renderer.render(&mut self.backend, &self.texture_manager)
|
||||||
self.renderer.render(self.context.gl(), &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 {
|
if self.is_editor && self.show_gizmos {
|
||||||
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
|
self.gizmo_renderer.render(&mut self.backend, &camera);
|
||||||
// Render axis indicator in corner
|
|
||||||
// 在角落渲染坐标轴指示器
|
|
||||||
self.gizmo_renderer.render_axis_indicator(
|
self.gizmo_renderer.render_axis_indicator(
|
||||||
self.context.gl(),
|
&mut self.backend,
|
||||||
self.context.width() as f32,
|
self.context.width() as f32,
|
||||||
self.context.height() 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).
|
/// This is used for overlay rendering (e.g., UI layer on top of world).
|
||||||
/// 用于叠加渲染(例如,UI 层叠加在世界上)。
|
/// 用于叠加渲染(例如,UI 层叠加在世界上)。
|
||||||
pub fn render_overlay(&mut self) -> Result<()> {
|
pub fn render_overlay(&mut self) -> Result<()> {
|
||||||
// Render sprites without clearing
|
self.renderer.render(&mut self.backend, &self.texture_manager)
|
||||||
// 渲染精灵但不清屏
|
.map_err(|e| crate::core::error::EngineError::WebGLError(e))
|
||||||
self.renderer.render(self.context.gl(), &self.texture_manager)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set scissor rect for clipping (screen coordinates, Y-down).
|
/// 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<()> {
|
pub fn render_to_viewport(&mut self, viewport_id: &str) -> Result<()> {
|
||||||
let viewport = match self.viewport_manager.get(viewport_id) {
|
let viewport = match self.viewport_manager.get(viewport_id) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get viewport settings
|
|
||||||
let show_grid = viewport.config.show_grid;
|
let show_grid = viewport.config.show_grid;
|
||||||
let show_gizmos = viewport.config.show_gizmos;
|
let show_gizmos = viewport.config.show_gizmos;
|
||||||
let camera = viewport.camera.clone();
|
let camera = viewport.camera.clone();
|
||||||
|
let (vp_width, vp_height) = viewport.dimensions();
|
||||||
|
|
||||||
// Bind viewport and clear
|
|
||||||
viewport.bind();
|
viewport.bind();
|
||||||
viewport.clear();
|
viewport.clear();
|
||||||
|
|
||||||
// Update renderer camera to match viewport camera
|
|
||||||
let renderer_camera = self.renderer.camera_mut();
|
let renderer_camera = self.renderer.camera_mut();
|
||||||
renderer_camera.position = camera.position;
|
renderer_camera.position = camera.position;
|
||||||
renderer_camera.set_zoom(camera.zoom);
|
renderer_camera.set_zoom(camera.zoom);
|
||||||
renderer_camera.rotation = camera.rotation;
|
renderer_camera.rotation = camera.rotation;
|
||||||
renderer_camera.set_viewport(camera.viewport_width(), camera.viewport_height());
|
renderer_camera.set_viewport(camera.viewport_width(), camera.viewport_height());
|
||||||
|
|
||||||
// Render grid if enabled - only in editor mode
|
|
||||||
// 渲染网格(如果启用)- 仅在编辑器模式下
|
|
||||||
if self.is_editor && show_grid {
|
if self.is_editor && show_grid {
|
||||||
self.grid_renderer.render(viewport.gl(), &camera);
|
self.grid_renderer.render(&mut self.backend, &camera);
|
||||||
self.grid_renderer.render_axes(viewport.gl(), &camera);
|
self.grid_renderer.render_axes(&mut self.backend, &camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render sprites
|
self.renderer.render(&mut self.backend, &self.texture_manager)
|
||||||
self.renderer.render(viewport.gl(), &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 {
|
if self.is_editor && show_gizmos {
|
||||||
self.gizmo_renderer.render(viewport.gl(), &camera);
|
self.gizmo_renderer.render(&mut self.backend, &camera);
|
||||||
// Render axis indicator in corner
|
self.gizmo_renderer.render_axis_indicator(&mut self.backend, vp_width as f32, vp_height as f32);
|
||||||
// 在角落渲染坐标轴指示器
|
|
||||||
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.clear();
|
self.gizmo_renderer.clear();
|
||||||
|
|
||||||
@@ -684,7 +744,8 @@ impl Engine {
|
|||||||
vertex_source: &str,
|
vertex_source: &str,
|
||||||
fragment_source: &str,
|
fragment_source: &str,
|
||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
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.
|
/// Compile a shader with a specific ID.
|
||||||
@@ -695,7 +756,8 @@ impl Engine {
|
|||||||
vertex_source: &str,
|
vertex_source: &str,
|
||||||
fragment_source: &str,
|
fragment_source: &str,
|
||||||
) -> Result<()> {
|
) -> 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.
|
/// Check if a shader exists.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
// Module declarations | 模块声明
|
// Module declarations | 模块声明
|
||||||
|
pub mod backend;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
@@ -45,6 +46,18 @@ pub mod input;
|
|||||||
// Re-exports | 重新导出
|
// Re-exports | 重新导出
|
||||||
pub use crate::core::{Engine, EngineConfig};
|
pub use crate::core::{Engine, EngineConfig};
|
||||||
pub use crate::core::error::{EngineError, Result};
|
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.
|
/// Initialize panic hook for better error messages in console.
|
||||||
/// 初始化panic hook以在控制台显示更好的错误信息。
|
/// 初始化panic hook以在控制台显示更好的错误信息。
|
||||||
@@ -819,4 +832,28 @@ impl GameEngine {
|
|||||||
.update_texture_region(id, x, y, width, height, pixels)
|
.update_texture_region(id, x, y, width, height, pixels)
|
||||||
.map_err(|e| JsValue::from_str(&e.to_string()))
|
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,149 +1,65 @@
|
|||||||
//! Sprite batch renderer for efficient 2D rendering.
|
//! Sprite batch renderer for efficient 2D rendering.
|
||||||
//! 用于高效2D渲染的精灵批处理渲染器。
|
|
||||||
|
|
||||||
use web_sys::{
|
use es_engine_shared::{
|
||||||
WebGl2RenderingContext, WebGlBuffer, WebGlVertexArrayObject,
|
traits::backend::{GraphicsBackend, BufferUsage},
|
||||||
|
types::{
|
||||||
|
handle::{BufferHandle, VertexArrayHandle},
|
||||||
|
vertex::{VertexLayout, VertexAttribute, VertexAttributeType},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core::error::{EngineError, Result};
|
|
||||||
use crate::math::Color;
|
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;
|
const VERTICES_PER_SPRITE: usize = 4;
|
||||||
|
|
||||||
/// Number of indices per sprite (2 triangles).
|
|
||||||
/// 每个精灵的索引数(2个三角形)。
|
|
||||||
const INDICES_PER_SPRITE: usize = 6;
|
const INDICES_PER_SPRITE: usize = 6;
|
||||||
|
const FLOATS_PER_VERTEX: usize = 9;
|
||||||
/// Transform data stride (x, y, rotation, scaleX, scaleY, originX, originY).
|
|
||||||
/// 变换数据步长。
|
|
||||||
const TRANSFORM_STRIDE: usize = 7;
|
const TRANSFORM_STRIDE: usize = 7;
|
||||||
|
|
||||||
/// UV data stride (u0, v0, u1, v1).
|
|
||||||
/// UV数据步长。
|
|
||||||
const UV_STRIDE: usize = 4;
|
const UV_STRIDE: usize = 4;
|
||||||
|
|
||||||
/// Batch key combining material and texture IDs.
|
|
||||||
/// 组合材质ID和纹理ID的批次键。
|
|
||||||
#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)]
|
#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)]
|
||||||
pub struct BatchKey {
|
pub struct BatchKey {
|
||||||
/// Material ID (0 = default material).
|
|
||||||
/// 材质ID(0 = 默认材质)。
|
|
||||||
pub material_id: u32,
|
pub material_id: u32,
|
||||||
/// Texture ID.
|
|
||||||
/// 纹理ID。
|
|
||||||
pub texture_id: u32,
|
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 {
|
pub struct SpriteBatch {
|
||||||
/// Vertex array object.
|
vbo: BufferHandle,
|
||||||
/// 顶点数组对象。
|
ibo: BufferHandle,
|
||||||
vao: WebGlVertexArrayObject,
|
vao: VertexArrayHandle,
|
||||||
|
|
||||||
/// Vertex buffer object.
|
|
||||||
/// 顶点缓冲区对象。
|
|
||||||
vbo: WebGlBuffer,
|
|
||||||
|
|
||||||
/// Index buffer object.
|
|
||||||
/// 索引缓冲区对象。
|
|
||||||
ibo: WebGlBuffer,
|
|
||||||
|
|
||||||
/// Maximum number of sprites.
|
|
||||||
/// 最大精灵数。
|
|
||||||
max_sprites: usize,
|
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<f32>)>,
|
batches: Vec<(BatchKey, Vec<f32>)>,
|
||||||
|
|
||||||
/// Total sprite count across all batches.
|
|
||||||
/// 所有批次的总精灵数。
|
|
||||||
sprite_count: usize,
|
sprite_count: usize,
|
||||||
|
|
||||||
/// Last batch key used, for determining if we can merge into the last batch.
|
|
||||||
/// 上一个使用的批次键,用于判断是否可以合并到最后一个批次。
|
|
||||||
last_batch_key: Option<BatchKey>,
|
last_batch_key: Option<BatchKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteBatch {
|
impl SpriteBatch {
|
||||||
/// Create a new sprite batch.
|
pub fn new(backend: &mut impl GraphicsBackend, max_sprites: usize) -> Result<Self, String> {
|
||||||
/// 创建新的精灵批处理器。
|
|
||||||
///
|
|
||||||
/// # Arguments | 参数
|
|
||||||
/// * `gl` - WebGL2 context | WebGL2上下文
|
|
||||||
/// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数
|
|
||||||
pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result<Self> {
|
|
||||||
// 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 | 分配顶点缓冲区内存
|
|
||||||
let vertex_buffer_size = max_sprites * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX * 4;
|
let vertex_buffer_size = max_sprites * VERTICES_PER_SPRITE * FLOATS_PER_VERTEX * 4;
|
||||||
gl.buffer_data_with_i32(
|
let vbo = backend.create_vertex_buffer(
|
||||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
&vec![0u8; vertex_buffer_size],
|
||||||
vertex_buffer_size as i32,
|
BufferUsage::Dynamic,
|
||||||
WebGl2RenderingContext::DYNAMIC_DRAW,
|
).map_err(|e| format!("VBO: {:?}", e))?;
|
||||||
);
|
|
||||||
|
|
||||||
// Create and populate index buffer | 创建并填充索引缓冲区
|
|
||||||
let ibo = gl
|
|
||||||
.create_buffer()
|
|
||||||
.ok_or(EngineError::BufferCreationFailed)?;
|
|
||||||
gl.bind_buffer(WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER, Some(&ibo));
|
|
||||||
|
|
||||||
let indices = Self::generate_indices(max_sprites);
|
let indices = Self::generate_indices(max_sprites);
|
||||||
unsafe {
|
let ibo = backend.create_index_buffer(
|
||||||
let index_array = js_sys::Uint16Array::view(&indices);
|
bytemuck::cast_slice(&indices),
|
||||||
gl.buffer_data_with_array_buffer_view(
|
BufferUsage::Static,
|
||||||
WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
|
).map_err(|e| format!("IBO: {:?}", e))?;
|
||||||
&index_array,
|
|
||||||
WebGl2RenderingContext::STATIC_DRAW,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up vertex attributes | 设置顶点属性
|
let layout = VertexLayout {
|
||||||
Self::setup_vertex_attributes(gl);
|
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
|
let vao = backend.create_vertex_array(vbo, Some(ibo), &layout)
|
||||||
gl.bind_vertex_array(None);
|
.map_err(|e| format!("VAO: {:?}", e))?;
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"SpriteBatch created with capacity: {} sprites | SpriteBatch创建完成,容量: {}个精灵",
|
|
||||||
max_sprites,
|
|
||||||
max_sprites
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
vao,
|
vbo, ibo, vao,
|
||||||
vbo,
|
|
||||||
ibo,
|
|
||||||
max_sprites,
|
max_sprites,
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
sprite_count: 0,
|
sprite_count: 0,
|
||||||
@@ -151,104 +67,19 @@ impl SpriteBatch {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate index buffer data.
|
|
||||||
/// 生成索引缓冲区数据。
|
|
||||||
fn generate_indices(max_sprites: usize) -> Vec<u16> {
|
fn generate_indices(max_sprites: usize) -> Vec<u16> {
|
||||||
let mut indices = Vec::with_capacity(max_sprites * INDICES_PER_SPRITE);
|
(0..max_sprites).flat_map(|i| {
|
||||||
|
|
||||||
for i in 0..max_sprites {
|
|
||||||
let base = (i * VERTICES_PER_SPRITE) as u16;
|
let base = (i * VERTICES_PER_SPRITE) as u16;
|
||||||
// Two triangles per sprite | 每个精灵两个三角形
|
[base, base + 1, base + 2, base + 2, base + 3, base]
|
||||||
// Triangle 1: 0, 1, 2 | 三角形1
|
}).collect()
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) {
|
pub fn clear(&mut self) {
|
||||||
self.batches.clear();
|
self.batches.clear();
|
||||||
self.sprite_count = 0;
|
self.sprite_count = 0;
|
||||||
self.last_batch_key = None;
|
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(
|
pub fn add_sprites(
|
||||||
&mut self,
|
&mut self,
|
||||||
transforms: &[f32],
|
transforms: &[f32],
|
||||||
@@ -256,252 +87,106 @@ impl SpriteBatch {
|
|||||||
uvs: &[f32],
|
uvs: &[f32],
|
||||||
colors: &[u32],
|
colors: &[u32],
|
||||||
material_ids: &[u32],
|
material_ids: &[u32],
|
||||||
_texture_manager: &TextureManager,
|
) -> Result<(), String> {
|
||||||
) -> Result<()> {
|
let count = texture_ids.len();
|
||||||
let sprite_count = texture_ids.len();
|
|
||||||
|
|
||||||
// Validate input data | 验证输入数据
|
if transforms.len() != count * TRANSFORM_STRIDE {
|
||||||
if transforms.len() != sprite_count * TRANSFORM_STRIDE {
|
return Err(format!("Transform mismatch: {} vs {}", transforms.len(), count * TRANSFORM_STRIDE));
|
||||||
return Err(EngineError::InvalidBatchData(format!(
|
}
|
||||||
"Transform data length mismatch: expected {}, got {}",
|
if uvs.len() != count * UV_STRIDE {
|
||||||
sprite_count * TRANSFORM_STRIDE,
|
return Err(format!("UV mismatch: {} vs {}", uvs.len(), count * UV_STRIDE));
|
||||||
transforms.len()
|
}
|
||||||
)));
|
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 {
|
for i in 0..count {
|
||||||
return Err(EngineError::InvalidBatchData(format!(
|
let t = i * TRANSFORM_STRIDE;
|
||||||
"UV data length mismatch: expected {}, got {}",
|
let uv = i * UV_STRIDE;
|
||||||
sprite_count * UV_STRIDE,
|
|
||||||
uvs.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if colors.len() != sprite_count {
|
let (x, y) = (transforms[t], transforms[t + 1]);
|
||||||
return Err(EngineError::InvalidBatchData(format!(
|
let rotation = transforms[t + 2];
|
||||||
"Color data length mismatch: expected {}, got {}",
|
let (width, height) = (transforms[t + 3], transforms[t + 4]);
|
||||||
sprite_count,
|
let (origin_x, origin_y) = (transforms[t + 5], transforms[t + 6]);
|
||||||
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 (u0, v0, u1, v1) = (uvs[uv], uvs[uv + 1], uvs[uv + 2], uvs[uv + 3]);
|
||||||
let color = Color::from_packed(colors[i]);
|
let color = Color::from_packed(colors[i]);
|
||||||
let color_arr = [color.r, color.g, color.b, color.a];
|
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
|
let key = BatchKey { material_id: material_ids[i], texture_id: texture_ids[i] };
|
||||||
// scale_x 和 scale_y 是实际显示尺寸
|
|
||||||
let width = scale_x;
|
|
||||||
let height = scale_y;
|
|
||||||
|
|
||||||
// Calculate aspect ratio (width / height), default 1.0 for degenerate cases
|
if self.last_batch_key != Some(key) {
|
||||||
// 计算宽高比(宽度/高度),退化情况下默认为 1.0
|
self.batches.push((key, Vec::new()));
|
||||||
let aspect_ratio = if height.abs() > 0.001 {
|
self.last_batch_key = Some(key);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to the last batch | 添加到最后一个批次
|
|
||||||
let batch = &mut self.batches.last_mut().unwrap().1;
|
let batch = &mut self.batches.last_mut().unwrap().1;
|
||||||
|
Self::add_sprite_vertices(batch, x, y, width, height, rotation, origin_x, origin_y,
|
||||||
// Calculate transformed vertices and add to batch | 计算变换后的顶点并添加到批次
|
u0, v0, u1, v1, color_arr, aspect);
|
||||||
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.sprite_count += sprite_count;
|
self.sprite_count += count;
|
||||||
Ok(())
|
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]
|
#[inline]
|
||||||
fn add_sprite_vertices_to_batch(
|
fn add_sprite_vertices(
|
||||||
batch: &mut Vec<f32>,
|
batch: &mut Vec<f32>,
|
||||||
x: f32,
|
x: f32, y: f32, width: f32, height: f32, rotation: f32,
|
||||||
y: f32,
|
origin_x: f32, origin_y: f32,
|
||||||
width: f32,
|
u0: f32, v0: f32, u1: f32, v1: f32,
|
||||||
height: f32,
|
color: [f32; 4], aspect: f32,
|
||||||
rotation: f32,
|
|
||||||
origin_x: f32,
|
|
||||||
origin_y: f32,
|
|
||||||
u0: f32,
|
|
||||||
v0: f32,
|
|
||||||
u1: f32,
|
|
||||||
v1: f32,
|
|
||||||
color: [f32; 4],
|
|
||||||
aspect_ratio: f32,
|
|
||||||
) {
|
) {
|
||||||
let cos = rotation.cos();
|
let (cos, sin) = (rotation.cos(), rotation.sin());
|
||||||
let sin = rotation.sin();
|
let (ox, oy) = (origin_x * width, origin_y * height);
|
||||||
|
|
||||||
// Origin offset | 原点偏移
|
let corners = [(-ox, height - oy), (width - ox, height - oy), (width - ox, -oy), (-ox, -oy)];
|
||||||
// origin (0,0) = bottom-left, (1,1) = top-right
|
let tex_coords = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];
|
||||||
// 原点 (0,0) = 左下角, (1,1) = 右上角
|
|
||||||
let ox = origin_x * width;
|
|
||||||
let oy = origin_y * height;
|
|
||||||
|
|
||||||
// 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 {
|
for i in 0..4 {
|
||||||
let (lx, ly) = corners[i];
|
let (lx, ly) = corners[i];
|
||||||
|
let (rx, ry) = (lx * cos - ly * sin, lx * sin + ly * cos);
|
||||||
// Apply rotation | 应用旋转
|
batch.extend_from_slice(&[rx + x, ry + y]);
|
||||||
let rx = lx * cos - ly * sin;
|
batch.extend_from_slice(&tex_coords[i]);
|
||||||
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 | 颜色
|
|
||||||
batch.extend_from_slice(&color);
|
batch.extend_from_slice(&color);
|
||||||
|
batch.push(aspect);
|
||||||
// Aspect ratio (same for all 4 vertices of a quad)
|
|
||||||
// 宽高比(四边形的 4 个顶点相同)
|
|
||||||
batch.push(aspect_ratio);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<f32>)] {
|
pub fn batches(&self) -> &[(BatchKey, Vec<f32>)] {
|
||||||
&self.batches
|
&self.batches
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flush a specific batch by index.
|
pub fn flush_batch(&self, backend: &mut impl GraphicsBackend, vertices: &[f32]) {
|
||||||
/// 按索引刷新特定批次。
|
if vertices.is_empty() { return; }
|
||||||
pub fn flush_batch_at(&self, gl: &WebGl2RenderingContext, index: usize) {
|
|
||||||
|
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) {
|
if let Some((_, vertices)) = self.batches.get(index) {
|
||||||
self.flush_batch(gl, vertices);
|
self.flush_batch(backend, vertices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current sprite count.
|
|
||||||
/// 获取当前精灵数量。
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn sprite_count(&self) -> usize {
|
pub fn sprite_count(&self) -> usize {
|
||||||
self.sprite_count
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,241 +1,210 @@
|
|||||||
//! Grid renderer for editor viewport.
|
//! Grid renderer for editor viewport.
|
||||||
//! 编辑器视口的网格渲染器。
|
|
||||||
|
|
||||||
use web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram};
|
use es_engine_shared::{
|
||||||
use crate::core::error::{Result, EngineError};
|
traits::backend::{GraphicsBackend, BufferUsage},
|
||||||
|
types::{
|
||||||
|
handle::{ShaderHandle, BufferHandle, VertexArrayHandle},
|
||||||
|
vertex::{VertexLayout, VertexAttribute, VertexAttributeType},
|
||||||
|
blend::BlendMode,
|
||||||
|
},
|
||||||
|
Vec4,
|
||||||
|
};
|
||||||
use super::camera::Camera2D;
|
use super::camera::Camera2D;
|
||||||
|
|
||||||
const GRID_VERTEX_SHADER: &str = r#"#version 300 es
|
const VERTEX_SHADER: &str = r#"#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
layout(location = 0) in vec2 a_position;
|
||||||
|
|
||||||
uniform mat3 u_projection;
|
uniform mat3 u_projection;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 pos = u_projection * vec3(a_position, 1.0);
|
vec3 pos = u_projection * vec3(a_position, 1.0);
|
||||||
gl_Position = vec4(pos.xy, 0.0, 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;
|
precision highp float;
|
||||||
|
|
||||||
uniform vec4 u_color;
|
uniform vec4 u_color;
|
||||||
|
|
||||||
out vec4 fragColor;
|
out vec4 fragColor;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
fragColor = u_color;
|
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 {
|
pub struct GridRenderer {
|
||||||
program: WebGlProgram,
|
shader: ShaderHandle,
|
||||||
vertex_buffer: WebGlBuffer,
|
grid_vbo: BufferHandle,
|
||||||
vertex_count: i32,
|
grid_vao: VertexArrayHandle,
|
||||||
last_zoom: f32,
|
axis_vbo: BufferHandle,
|
||||||
last_width: f32,
|
axis_vao: VertexArrayHandle,
|
||||||
last_height: f32,
|
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 {
|
impl GridRenderer {
|
||||||
pub fn new(gl: &WebGl2RenderingContext) -> Result<Self> {
|
pub fn new(backend: &mut impl GraphicsBackend) -> Result<Self, String> {
|
||||||
let program = Self::create_program(gl)?;
|
let shader = backend.compile_shader(VERTEX_SHADER, FRAGMENT_SHADER)
|
||||||
let vertex_buffer = gl.create_buffer()
|
.map_err(|e| format!("Grid shader: {:?}", e))?;
|
||||||
.ok_or(EngineError::BufferCreationFailed)?;
|
|
||||||
|
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 {
|
Ok(Self {
|
||||||
program,
|
shader,
|
||||||
vertex_buffer,
|
grid_vbo,
|
||||||
vertex_count: 0,
|
grid_vao,
|
||||||
last_zoom: 0.0,
|
axis_vbo,
|
||||||
last_width: 0.0,
|
axis_vao,
|
||||||
last_height: 0.0,
|
grid_vertex_count: 0,
|
||||||
|
cache: GridCache::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_program(gl: &WebGl2RenderingContext) -> Result<WebGlProgram> {
|
pub fn render(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) {
|
||||||
let vert_shader = gl.create_shader(WebGl2RenderingContext::VERTEX_SHADER)
|
self.update_grid_if_needed(backend, camera);
|
||||||
.ok_or_else(|| EngineError::ShaderCompileFailed("Failed to create vertex shader".into()))?;
|
|
||||||
gl.shader_source(&vert_shader, GRID_VERTEX_SHADER);
|
|
||||||
gl.compile_shader(&vert_shader);
|
|
||||||
|
|
||||||
if !gl.get_shader_parameter(&vert_shader, WebGl2RenderingContext::COMPILE_STATUS)
|
if self.grid_vertex_count == 0 {
|
||||||
.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
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_zoom = zoom;
|
backend.bind_shader(self.shader).ok();
|
||||||
self.last_width = width;
|
backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok();
|
||||||
self.last_height = height;
|
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);
|
pub fn render_axes(&mut self, backend: &mut impl GraphicsBackend, camera: &Camera2D) {
|
||||||
let half_height = height / (2.0 * zoom);
|
let axis_length = self.calculate_axis_length(camera);
|
||||||
let max_size = half_width.max(half_height) * 2.0;
|
self.update_axis_buffer(backend, axis_length);
|
||||||
|
|
||||||
let base_step = if max_size > 10000.0 {
|
backend.bind_shader(self.shader).ok();
|
||||||
1000.0
|
backend.set_uniform_mat3("u_projection", &camera.projection_matrix()).ok();
|
||||||
} else if max_size > 1000.0 {
|
backend.set_blend_mode(BlendMode::Alpha);
|
||||||
100.0
|
|
||||||
} else if max_size > 100.0 {
|
|
||||||
10.0
|
|
||||||
} else if max_size > 10.0 {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
0.1
|
|
||||||
};
|
|
||||||
|
|
||||||
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<f32> {
|
||||||
|
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 range = max_size * 1.5;
|
||||||
let start = -range;
|
|
||||||
let end = range;
|
|
||||||
|
|
||||||
let mut vertices = Vec::new();
|
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;
|
let mut pos = start;
|
||||||
while x <= end {
|
while pos <= end {
|
||||||
vertices.extend_from_slice(&[x, start, x, end]);
|
vertices.extend_from_slice(&[pos, -range, pos, range]);
|
||||||
x += fine_step;
|
vertices.extend_from_slice(&[-range, pos, range, pos]);
|
||||||
|
pos += step;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut y = (start / fine_step).floor() * fine_step;
|
vertices
|
||||||
while y <= end {
|
|
||||||
vertices.extend_from_slice(&[start, y, end, y]);
|
|
||||||
y += fine_step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.vertex_count = (vertices.len() / 2) as i32;
|
fn calculate_grid_step(max_size: f32) -> f32 {
|
||||||
|
match max_size {
|
||||||
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer));
|
s if s > 10000.0 => 1000.0,
|
||||||
unsafe {
|
s if s > 1000.0 => 100.0,
|
||||||
let array = js_sys::Float32Array::view(&vertices);
|
s if s > 100.0 => 10.0,
|
||||||
gl.buffer_data_with_array_buffer_view(
|
s if s > 10.0 => 1.0,
|
||||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
_ => 0.1,
|
||||||
&array,
|
|
||||||
WebGl2RenderingContext::DYNAMIC_DRAW,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) {
|
fn calculate_axis_length(&self, camera: &Camera2D) -> f32 {
|
||||||
self.update_grid(gl, camera);
|
let half_w = camera.viewport_width() / (2.0 * camera.zoom);
|
||||||
|
let half_h = camera.viewport_height() / (2.0 * camera.zoom);
|
||||||
if self.vertex_count == 0 {
|
half_w.max(half_h) * 2.0
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.use_program(Some(&self.program));
|
fn build_axis_vertices(length: f32) -> Vec<f32> {
|
||||||
|
vec![
|
||||||
let projection = camera.projection_matrix();
|
-length, 0.0, length, 0.0,
|
||||||
let proj_loc = gl.get_uniform_location(&self.program, "u_projection");
|
0.0, -length, 0.0, length,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_axes(&self, gl: &WebGl2RenderingContext, camera: &Camera2D) {
|
fn update_axis_buffer(&mut self, backend: &mut impl GraphicsBackend, length: f32) {
|
||||||
gl.use_program(Some(&self.program));
|
let data = Self::build_axis_vertices(length);
|
||||||
|
backend.update_buffer(self.axis_vbo, 0, bytemuck::cast_slice(&data)).ok();
|
||||||
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 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)
|
pub fn destroy(self, backend: &mut impl GraphicsBackend) {
|
||||||
let y_axis = [0.0, -axis_length, 0.0, axis_length];
|
backend.destroy_vertex_array(self.grid_vao);
|
||||||
unsafe {
|
backend.destroy_vertex_array(self.axis_vao);
|
||||||
let array = js_sys::Float32Array::view(&y_axis);
|
backend.destroy_buffer(self.grid_vbo);
|
||||||
gl.buffer_data_with_array_buffer_view(
|
backend.destroy_buffer(self.axis_vbo);
|
||||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
backend.destroy_shader(self.shader);
|
||||||
&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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,96 @@
|
|||||||
//! Main 2D renderer implementation.
|
//! Main 2D renderer implementation.
|
||||||
//! 主2D渲染器实现。
|
|
||||||
|
|
||||||
use wasm_bindgen::JsCast;
|
use es_engine_shared::{
|
||||||
use web_sys::WebGl2RenderingContext;
|
traits::backend::GraphicsBackend,
|
||||||
|
types::{
|
||||||
use crate::core::error::Result;
|
handle::ShaderHandle,
|
||||||
use crate::resource::TextureManager;
|
blend::ScissorRect,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::backend::WebGL2Backend;
|
||||||
use super::batch::SpriteBatch;
|
use super::batch::SpriteBatch;
|
||||||
use super::camera::Camera2D;
|
use super::camera::Camera2D;
|
||||||
use super::shader::ShaderManager;
|
use super::texture::TextureManager;
|
||||||
use super::material::MaterialManager;
|
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 {
|
pub struct Renderer2D {
|
||||||
/// Sprite batch renderer.
|
|
||||||
/// 精灵批处理渲染器。
|
|
||||||
sprite_batch: SpriteBatch,
|
sprite_batch: SpriteBatch,
|
||||||
|
default_shader: ShaderHandle,
|
||||||
/// Shader manager.
|
custom_shaders: HashMap<u32, ShaderHandle>,
|
||||||
/// 着色器管理器。
|
next_shader_id: u32,
|
||||||
shader_manager: ShaderManager,
|
materials: HashMap<u32, Material>,
|
||||||
|
|
||||||
/// Material manager.
|
|
||||||
/// 材质管理器。
|
|
||||||
material_manager: MaterialManager,
|
|
||||||
|
|
||||||
/// 2D camera.
|
|
||||||
/// 2D相机。
|
|
||||||
camera: Camera2D,
|
camera: Camera2D,
|
||||||
|
|
||||||
/// Clear color (RGBA).
|
|
||||||
/// 清除颜色 (RGBA)。
|
|
||||||
clear_color: [f32; 4],
|
clear_color: [f32; 4],
|
||||||
|
scissor_rect: Option<ScissorRect>,
|
||||||
/// 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.
|
|
||||||
/// 视口高度,用于裁剪坐标转换。
|
|
||||||
viewport_height: f32,
|
viewport_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer2D {
|
impl Renderer2D {
|
||||||
/// Create a new 2D renderer.
|
pub fn new(backend: &mut WebGL2Backend, max_sprites: usize) -> Result<Self, String> {
|
||||||
/// 创建新的2D渲染器。
|
let sprite_batch = SpriteBatch::new(backend, max_sprites)?;
|
||||||
///
|
let default_shader = backend.compile_shader(SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER)
|
||||||
/// # Arguments | 参数
|
.map_err(|e| format!("Default shader: {:?}", e))?;
|
||||||
/// * `gl` - WebGL2 context | WebGL2上下文
|
|
||||||
/// * `max_sprites` - Maximum sprites per batch | 每批次最大精灵数
|
|
||||||
pub fn new(gl: &WebGl2RenderingContext, max_sprites: usize) -> Result<Self> {
|
|
||||||
let sprite_batch = SpriteBatch::new(gl, max_sprites)?;
|
|
||||||
let shader_manager = ShaderManager::new(gl)?;
|
|
||||||
let material_manager = MaterialManager::new();
|
|
||||||
|
|
||||||
// Get canvas size for camera | 获取canvas尺寸用于相机
|
let (width, height) = (backend.width() as f32, backend.height() as f32);
|
||||||
let canvas = gl.canvas()
|
let camera = Camera2D::new(width, height);
|
||||||
.and_then(|c| c.dyn_into::<web_sys::HtmlCanvasElement>().ok())
|
|
||||||
.map(|c| (c.width() as f32, c.height() as f32))
|
|
||||||
.unwrap_or((800.0, 600.0));
|
|
||||||
|
|
||||||
let camera = Camera2D::new(canvas.0, canvas.1);
|
let mut materials = HashMap::new();
|
||||||
|
materials.insert(0, Material::default());
|
||||||
log::info!(
|
|
||||||
"Renderer2D initialized | Renderer2D初始化完成: {}x{}, max sprites: {}",
|
|
||||||
canvas.0, canvas.1, max_sprites
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
sprite_batch,
|
sprite_batch,
|
||||||
shader_manager,
|
default_shader,
|
||||||
material_manager,
|
custom_shaders: HashMap::new(),
|
||||||
|
next_shader_id: 100,
|
||||||
|
materials,
|
||||||
camera,
|
camera,
|
||||||
clear_color: [0.1, 0.1, 0.12, 1.0],
|
clear_color: [0.1, 0.1, 0.12, 1.0],
|
||||||
current_shader_id: 0,
|
|
||||||
current_material_id: 0,
|
|
||||||
scissor_rect: None,
|
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(
|
pub fn submit_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
transforms: &[f32],
|
transforms: &[f32],
|
||||||
@@ -112,277 +98,171 @@ impl Renderer2D {
|
|||||||
uvs: &[f32],
|
uvs: &[f32],
|
||||||
colors: &[u32],
|
colors: &[u32],
|
||||||
material_ids: &[u32],
|
material_ids: &[u32],
|
||||||
texture_manager: &TextureManager,
|
) -> Result<(), String> {
|
||||||
) -> Result<()> {
|
self.sprite_batch.add_sprites(transforms, texture_ids, uvs, colors, material_ids)
|
||||||
self.sprite_batch.add_sprites(
|
|
||||||
transforms,
|
|
||||||
texture_ids,
|
|
||||||
uvs,
|
|
||||||
colors,
|
|
||||||
material_ids,
|
|
||||||
texture_manager,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the current frame.
|
pub fn render(&mut self, backend: &mut WebGL2Backend, texture_manager: &TextureManager) -> Result<(), String> {
|
||||||
/// 渲染当前帧。
|
|
||||||
pub fn render(&mut self, gl: &WebGl2RenderingContext, texture_manager: &TextureManager) -> Result<()> {
|
|
||||||
if self.sprite_batch.sprite_count() == 0 {
|
if self.sprite_batch.sprite_count() == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply scissor test if enabled
|
self.apply_scissor(backend);
|
||||||
// 如果启用,应用裁剪测试
|
|
||||||
self.apply_scissor(gl);
|
|
||||||
|
|
||||||
// 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 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() {
|
for batch_idx in 0..self.sprite_batch.batches().len() {
|
||||||
let (batch_key, vertices) = &self.sprite_batch.batches()[batch_idx];
|
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 {
|
if batch_key.material_id != current_material_id {
|
||||||
current_material_id = batch_key.material_id;
|
current_material_id = batch_key.material_id;
|
||||||
|
|
||||||
// Get material (fallback to default if not found) | 获取材质(未找到则回退到默认)
|
let material = self.materials.get(&batch_key.material_id)
|
||||||
let material = self.material_manager.get_material(batch_key.material_id)
|
.cloned()
|
||||||
.unwrap_or_else(|| self.material_manager.get_default_material());
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Bind shader | 绑定Shader
|
let shader = if material.shader_id == 0 {
|
||||||
let shader = self.shader_manager.get_shader(material.shader_id)
|
self.default_shader
|
||||||
.unwrap_or_else(|| self.shader_manager.get_default_shader());
|
} else {
|
||||||
shader.bind(gl);
|
self.custom_shaders.get(&material.shader_id)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(self.default_shader)
|
||||||
|
};
|
||||||
|
|
||||||
// Apply blend mode | 应用混合模式
|
backend.bind_shader(shader).ok();
|
||||||
MaterialManager::apply_blend_mode(gl, material.blend_mode);
|
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 | 设置投影矩阵
|
for name in material.uniforms.names() {
|
||||||
shader.set_uniform_mat3(gl, "u_projection", &projection.to_cols_array());
|
if let Some(value) = material.uniforms.get(name) {
|
||||||
|
match value {
|
||||||
// Set texture sampler | 设置纹理采样器
|
UniformValue::Float(v) => { backend.set_uniform_f32(name, *v).ok(); }
|
||||||
shader.set_uniform_i32(gl, "u_texture", 0);
|
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(); }
|
||||||
// Apply material uniforms | 应用材质uniform
|
UniformValue::Vec4(v) => { backend.set_uniform_vec4(name, es_engine_shared::Vec4::new(v[0], v[1], v[2], v[3])).ok(); }
|
||||||
material.uniforms.apply_to_shader(gl, shader);
|
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 {
|
if batch_key.texture_id != current_texture_id {
|
||||||
current_texture_id = batch_key.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(backend, batch_idx);
|
||||||
self.sprite_batch.flush_batch_at(gl, batch_idx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear batch for next frame | 清空批处理以供下一帧使用
|
|
||||||
self.sprite_batch.clear();
|
self.sprite_batch.clear();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to camera.
|
fn apply_scissor(&self, backend: &mut WebGL2Backend) {
|
||||||
/// 获取相机的可变引用。
|
if let Some(rect) = &self.scissor_rect {
|
||||||
#[inline]
|
backend.set_scissor(Some(ScissorRect {
|
||||||
pub fn camera_mut(&mut self) -> &mut Camera2D {
|
x: rect.x,
|
||||||
&mut self.camera
|
y: rect.y,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
backend.set_scissor(None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to camera.
|
|
||||||
/// 获取相机的引用。
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn camera(&self) -> &Camera2D {
|
pub fn camera_mut(&mut self) -> &mut Camera2D { &mut self.camera }
|
||||||
&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) {
|
pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) {
|
||||||
self.clear_color = [r, g, b, a];
|
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) {
|
pub fn resize(&mut self, width: f32, height: f32) {
|
||||||
self.camera.set_viewport(width, height);
|
self.camera.set_viewport(width, height);
|
||||||
self.viewport_height = 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) {
|
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) {
|
pub fn compile_shader(&mut self, backend: &mut WebGL2Backend, vertex: &str, fragment: &str) -> Result<u32, String> {
|
||||||
self.scissor_rect = None;
|
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.
|
pub fn compile_shader_with_id(&mut self, backend: &mut WebGL2Backend, id: u32, vertex: &str, fragment: &str) -> Result<(), String> {
|
||||||
/// 应用当前裁剪状态到 GL 上下文。
|
let handle = backend.compile_shader(vertex, fragment)
|
||||||
fn apply_scissor(&self, gl: &WebGl2RenderingContext) {
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
if let Some([x, y, width, height]) = self.scissor_rect {
|
self.custom_shaders.insert(id, handle);
|
||||||
gl.enable(WebGl2RenderingContext::SCISSOR_TEST);
|
Ok(())
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= Shader Management =============
|
pub fn has_shader(&self, id: u32) -> bool {
|
||||||
// ============= 着色器管理 =============
|
id == 0 || self.custom_shaders.contains_key(&id)
|
||||||
|
|
||||||
/// 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<u32> {
|
|
||||||
self.shader_manager.compile_shader(gl, vertex_source, fragment_source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a shader with a specific ID.
|
pub fn remove_shader(&mut self, id: u32) -> bool {
|
||||||
/// 使用特定ID编译着色器。
|
if id < 100 { return false; }
|
||||||
pub fn compile_shader_with_id(
|
self.custom_shaders.remove(&id).is_some()
|
||||||
&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 register_material(&mut self, material: Material) -> u32 {
|
||||||
/// 检查着色器是否存在。
|
let id = self.materials.keys().max().unwrap_or(&0) + 1;
|
||||||
pub fn has_shader(&self, shader_id: u32) -> bool {
|
self.materials.insert(id, material);
|
||||||
self.shader_manager.has_shader(shader_id)
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a shader.
|
pub fn register_material_with_id(&mut self, id: u32, material: Material) {
|
||||||
/// 移除着色器。
|
self.materials.insert(id, material);
|
||||||
pub fn remove_shader(&mut self, shader_id: u32) -> bool {
|
|
||||||
self.shader_manager.remove_shader(shader_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get shader manager reference.
|
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 shader_manager(&self) -> &ShaderManager {
|
pub fn has_material(&self, id: u32) -> bool { self.materials.contains_key(&id) }
|
||||||
&self.shader_manager
|
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable shader manager reference.
|
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) {
|
||||||
pub fn shader_manager_mut(&mut self) -> &mut ShaderManager {
|
mat.uniforms.set_vec4(name, x, y, z, w);
|
||||||
&mut self.shader_manager
|
true
|
||||||
|
} else { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= Material Management =============
|
pub fn destroy(self, backend: &mut WebGL2Backend) {
|
||||||
// ============= 材质管理 =============
|
self.sprite_batch.destroy(backend);
|
||||||
|
backend.destroy_shader(self.default_shader);
|
||||||
/// Register a custom material.
|
for (_, handle) in self.custom_shaders {
|
||||||
/// 注册自定义材质。
|
backend.destroy_shader(handle);
|
||||||
///
|
|
||||||
/// # 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use wasm_bindgen::JsCast;
|
|||||||
use web_sys::{HtmlImageElement, WebGl2RenderingContext, WebGlTexture};
|
use web_sys::{HtmlImageElement, WebGl2RenderingContext, WebGlTexture};
|
||||||
|
|
||||||
use crate::core::error::{EngineError, Result};
|
use crate::core::error::{EngineError, Result};
|
||||||
|
use crate::backend::WebGL2Backend;
|
||||||
use super::Texture;
|
use super::Texture;
|
||||||
|
|
||||||
/// 纹理加载状态
|
/// 纹理加载状态
|
||||||
@@ -279,8 +280,6 @@ impl TextureManager {
|
|||||||
if let Some(texture) = self.textures.get(&id) {
|
if let Some(texture) = self.textures.get(&id) {
|
||||||
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture.handle));
|
self.gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture.handle));
|
||||||
} else if let Some(default) = &self.default_texture {
|
} else if let Some(default) = &self.default_texture {
|
||||||
// ID 0 is the default texture, no warning needed
|
|
||||||
// ID 0 是默认纹理,不需要警告
|
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
log::warn!("Texture {} not found, using default | 未找到纹理 {},使用默认纹理", id, id);
|
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.
|
/// Check if texture is loaded.
|
||||||
/// 检查纹理是否已加载。
|
/// 检查纹理是否已加载。
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
Reference in New Issue
Block a user