From ca86b15415c0c554f8142574ac367b4100c3ab1f Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Tue, 15 Oct 2024 18:08:33 -0700 Subject: [PATCH] Ocarina of Time 3D: Rewrite texture loading to remove TextureHolder and remove the name-based binding --- src/OcarinaOfTime3D/cmab.ts | 19 +-- src/OcarinaOfTime3D/cmb.ts | 8 +- src/OcarinaOfTime3D/ctxb.ts | 2 +- src/OcarinaOfTime3D/lm3d_scenes.ts | 53 ++++---- src/OcarinaOfTime3D/mm3d_scenes.ts | 41 +++--- src/OcarinaOfTime3D/oot3d_scenes.ts | 76 ++++++----- src/OcarinaOfTime3D/render.ts | 195 +++++++++++++++------------- src/OcarinaOfTime3D/scenes.ts | 52 ++------ src/Scenes_Test.ts | 19 --- 9 files changed, 221 insertions(+), 244 deletions(-) diff --git a/src/OcarinaOfTime3D/cmab.ts b/src/OcarinaOfTime3D/cmab.ts index 1bd4ffcdd..11ef0aaf1 100644 --- a/src/OcarinaOfTime3D/cmab.ts +++ b/src/OcarinaOfTime3D/cmab.ts @@ -7,10 +7,9 @@ import { Color, colorFromRGBA } from "../Color.js"; import { Texture, TextureLevel, Version, calcTexMtx } from "./cmb.js"; import { decodeTexture, computeTextureByteSize, getTextureFormatFromGLFormat } from "./pica_texture.js"; import { getPointHermite } from "../Spline.js"; -import { TextureMapping } from "../TextureHolder.js"; -import { CtrTextureHolder } from "./render.js"; +import { CmabData } from "./render.js"; import { lerp } from "../MathHelpers.js"; -import { GfxTextureDimension } from "../gfx/platform/GfxPlatform.js"; +import { GfxTexture, GfxTextureDimension } from "../gfx/platform/GfxPlatform.js"; // CMAB (CTR Material Animation Binary) // Seems to be inspired by the .cmata file format. Perhaps an earlier version of NW4C used it? @@ -493,13 +492,17 @@ export class ColorAnimator { } export class TexturePaletteAnimator { - constructor(public animationController: AnimationController, public cmab: CMAB, public animEntry: AnimationEntry) { + constructor(public animationController: AnimationController, public cmabData: CmabData, public animEntry: AnimationEntry) { assert(animEntry.animationType === AnimationType.TexturePalette); } - public fillTextureMapping(textureHolder: CtrTextureHolder, textureMapping: TextureMapping): void { - const animFrame = getAnimFrame(this.cmab, this.animationController.getTimeInFrames()); - const textureIndex = sampleAnimationTrack(this.animEntry.tracks[0], animFrame); - textureHolder.fillTextureMapping(textureMapping, this.cmab.textures[textureIndex].name); + private getTextureIndex(): number { + const animFrame = getAnimFrame(this.cmabData.cmab, this.animationController.getTimeInFrames()); + return sampleAnimationTrack(this.animEntry.tracks[0], animFrame); + } + + public getTexture(): GfxTexture { + const textureIdx = this.getTextureIndex(); + return this.cmabData.textureData[textureIdx].gfxTexture; } } diff --git a/src/OcarinaOfTime3D/cmb.ts b/src/OcarinaOfTime3D/cmb.ts index e4af1985b..627c3b3de 100644 --- a/src/OcarinaOfTime3D/cmb.ts +++ b/src/OcarinaOfTime3D/cmb.ts @@ -643,7 +643,7 @@ export interface Texture { levels: TextureLevel[]; } -export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null, cmbName: string = ''): Texture[] { +export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null): Texture[] { const view = buffer.createDataView(); assert(readString(buffer, 0x00, 0x04) === 'tex '); @@ -663,9 +663,7 @@ export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlic const glFormat = view.getUint32(offs + 0x0C, true); let dataOffs = view.getUint32(offs + 0x10, true); let dataEnd = dataOffs + size; - const texName = readString(buffer, offs + 0x14, 0x10); - // TODO(jstpierre): Maybe find another way to dedupe? Name seems inconsistent. - const name = `${cmbName}/${i}/${texName}`; + const name = readString(buffer, offs + 0x14, 0x10); offs += 0x24; const levels: TextureLevel[] = []; @@ -722,7 +720,7 @@ export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlic } function readTexChunk(cmb: CMB, buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null): void { - cmb.textures = parseTexChunk(buffer, texData, cmb.name); + cmb.textures = parseTexChunk(buffer, texData); } function readLutsChunk(cmb: CMB, buffer: ArrayBufferSlice): void { diff --git a/src/OcarinaOfTime3D/ctxb.ts b/src/OcarinaOfTime3D/ctxb.ts index fdd806469..27d3983f0 100644 --- a/src/OcarinaOfTime3D/ctxb.ts +++ b/src/OcarinaOfTime3D/ctxb.ts @@ -16,6 +16,6 @@ export function parse(buffer: ArrayBufferSlice): CTXB { const texChunkOffs = view.getUint32(0x10, true); const texDataOffs = view.getUint32(0x14, true); - const textures = parseTexChunk(buffer.slice(texChunkOffs), buffer.slice(texDataOffs), 'ctxb'); + const textures = parseTexChunk(buffer.slice(texChunkOffs), buffer.slice(texDataOffs)); return { textures }; } diff --git a/src/OcarinaOfTime3D/lm3d_scenes.ts b/src/OcarinaOfTime3D/lm3d_scenes.ts index 24e3d2b0c..438057d78 100644 --- a/src/OcarinaOfTime3D/lm3d_scenes.ts +++ b/src/OcarinaOfTime3D/lm3d_scenes.ts @@ -10,12 +10,12 @@ import * as CTXB from './ctxb.js'; import * as Viewer from '../viewer.js'; -import { CmbInstance, CmbData } from './render.js'; +import { CmbInstance, CmbData, CmabData } from './render.js'; import { SceneGroup } from '../viewer.js'; -import { leftPad, assertExists, nArray, assert } from '../util.js'; +import { leftPad, assertExists, assert } from '../util.js'; import { GfxDevice } from '../gfx/platform/GfxPlatform.js'; -import { GrezzoTextureHolder, MultiCmbScene } from './scenes.js'; -import { computeModelMatrixSRT, scaleMatrix } from '../MathHelpers.js'; +import { MultiCmbScene } from './scenes.js'; +import { computeModelMatrixSRT } from '../MathHelpers.js'; import { SceneContext } from '../SceneBase.js'; import { ZSIEnvironmentSettings } from './zsi.js'; import { colorFromRGBA } from '../Color.js'; @@ -32,7 +32,7 @@ function bcsvHashLM(str: string): number { const r0 = ((((hash - r6) / 2) + r6) >> 24); hash -= (r0 * 33554393); } - + return hash; } @@ -63,7 +63,6 @@ class SceneDesc implements Viewer.SceneDesc { const path_gar = `${pathBase}/map/map${leftPad(''+this.mapNumber, 2, '0')}.gar`; const models_path = `${pathBase}/mapmdl/map${this.mapNumber}`; - const textureHolder = new GrezzoTextureHolder(); const dataFetcher = context.dataFetcher; const spawnVrbox = (renderer: MultiCmbScene, cache: GfxRenderCache, garName: string) => { @@ -71,13 +70,12 @@ class SceneDesc implements Viewer.SceneDesc { const vrGar = ZAR.parse(garBuffer); const firstCMB = assertExists(vrGar.files.find((file) => file.name.endsWith('.cmb'))); - + const cmb = CMB.parse(firstCMB.buffer); const cmbData = new CmbData(cache, cmb); - textureHolder.addCMB(device, cmb); renderer.cmbData.push(cmbData); - const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name); + const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name); cmbRenderer.isSkybox = true; renderer.skyRenderers.push(cmbRenderer); @@ -86,12 +84,13 @@ class SceneDesc implements Viewer.SceneDesc { for (let i = 0; i < cmabFiles.length; i++) { const cmab = CMAB.parse(CMB.Version.LuigisMansion, cmabFiles[i].buffer); - textureHolder.addTextures(device, cmab.textures); - cmbRenderer.bindCMAB(cmab); + const cmabData = new CmabData(cache, cmab); + + cmbRenderer.bindCMAB(cmabData); } const csabFile = vrGar.files.find((file) => file.name === `${cmbBasename}.csab`); - if (csabFile){ + if (csabFile !== undefined) { cmbRenderer.bindCSAB(CSAB.parse(CMB.Version.LuigisMansion, csabFile.buffer)); } }); @@ -113,7 +112,7 @@ class SceneDesc implements Viewer.SceneDesc { const modelCache = new Map(); - const renderer = new MultiCmbScene(device, textureHolder); + const renderer = new MultiCmbScene(device); const cache = renderer.getRenderCache(); const promises: Promise[] = []; const envSettingsMap: Map = new Map(); @@ -127,7 +126,6 @@ class SceneDesc implements Viewer.SceneDesc { default: spawnVrbox(renderer, cache, "vrball_m.gar"); break; } - //TODO(M-1): This isn't right but it works for now for (let i = 0; i < lightInfo.records.length; i++) { const record = lightInfo.records[i]; @@ -143,7 +141,7 @@ class SceneDesc implements Viewer.SceneDesc { let envSettings = envSettingsMap.get(roomNum) as ZSIEnvironmentSettings; const light = envSettings.lights[index]; - + const diffuseR = assertExists(getField(lightInfo, record, "diffuse_x")) / 0xFF; const diffuseG = assertExists(getField(lightInfo, record, "diffuse_y")) / 0xFF; const diffuseB = assertExists(getField(lightInfo, record, "diffuse_z")) / 0xFF; @@ -187,18 +185,26 @@ class SceneDesc implements Viewer.SceneDesc { //const skyboxType = assertExists(getField(roomInfo, roomInfo.records[i], "VRboxType")); //assert(skyboxType === 0); - // TODO(jstpierre): How does the engine know which CMB file to spawn? const firstCMB = assertExists(roomGar.files.find((file) => file.name.endsWith('.cmb'))); const cmb = CMB.parse(firstCMB.buffer); const ctxbFiles = roomGar.files.filter((file) => file.name.endsWith('.ctxb')); + // Patch textures into in CMB file. for (let i = 0; i < ctxbFiles.length; i++) { const ctxb = CTXB.parse(ctxbFiles[i].buffer); - textureHolder.addCTXB(device, ctxb); + for (let j = 0; j < ctxb.textures.length; j++) { + const texture = ctxb.textures[j]; + + const cmbTexture = cmb.textures.find((tex) => tex.name === texture.name); + if (cmbTexture === undefined) + continue; + + assert(cmbTexture.levels.length === 0); + cmbTexture.levels = texture.levels; + } } const cmbData = new CmbData(cache, cmb); - textureHolder.addCMB(device, cmb); renderer.cmbData.push(cmbData); let envSettings = envSettingsMap.get(i) as ZSIEnvironmentSettings; @@ -206,8 +212,8 @@ class SceneDesc implements Viewer.SceneDesc { if(envSettings === undefined){ envSettings = envSettingsMap.get(0) as ZSIEnvironmentSettings; } - - const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name); + + const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name); cmbRenderer.setEnvironmentSettings(envSettings); renderer.cmbRenderers.push(cmbRenderer); @@ -215,8 +221,8 @@ class SceneDesc implements Viewer.SceneDesc { const cmabFile = roomGar.files.find((file) => file.name === `${cmbBasename}.cmab`); if (cmabFile) { const cmab = CMAB.parse(CMB.Version.LuigisMansion, cmabFile.buffer); - textureHolder.addTextures(device, cmab.textures); - cmbRenderer.bindCMAB(cmab); + const cmabData = new CmabData(cache, cmab); + cmbRenderer.bindCMAB(cmabData); } const roomFurnitureEntries: BCSV.Bcsv = getEntriesWithField(furnitureInfo, "room_no", i); @@ -234,12 +240,11 @@ class SceneDesc implements Viewer.SceneDesc { if (cmbData === undefined) { const cmb = CMB.parse(cmbFile.buffer); cmbData = new CmbData(cache, cmb); - textureHolder.addTextures(device, cmb.textures); renderer.cmbData.push(cmbData); modelCache.set(cmbFilename, cmbData); } - const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name); + const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name); cmbRenderer.setEnvironmentSettings(envSettings); const rotationX = assertExists(getField(roomFurnitureEntries, record, "dir_x")) / 180 * Math.PI; diff --git a/src/OcarinaOfTime3D/mm3d_scenes.ts b/src/OcarinaOfTime3D/mm3d_scenes.ts index bf19f40cb..e7b4beac2 100644 --- a/src/OcarinaOfTime3D/mm3d_scenes.ts +++ b/src/OcarinaOfTime3D/mm3d_scenes.ts @@ -7,12 +7,12 @@ import * as ZSI from './zsi.js'; import * as Viewer from '../viewer.js'; -import { RoomRenderer, CtrTextureHolder, CmbInstance } from './render.js'; +import { RoomRenderer, CmbInstance, CmabData } from './render.js'; import { SceneGroup } from '../viewer.js'; import { assert, assertExists, hexzero } from '../util.js'; import { GfxDevice } from '../gfx/platform/GfxPlatform.js'; -import { OoT3DRenderer, ModelCache } from './oot3d_scenes.js'; -import { TransparentBlack, colorFromRGBA, colorNewFromRGBA } from '../Color.js'; +import { OoT3DRenderer, ModelCache, DataHolder } from './oot3d_scenes.js'; +import { TransparentBlack } from '../Color.js'; import { mat4 } from 'gl-matrix'; import AnimationController from '../AnimationController.js'; import { SceneContext } from '../SceneBase.js'; @@ -601,16 +601,14 @@ class SceneDesc implements Viewer.SceneDesc { this.id = id; } - private async spawnActorForRoom(renderer: OoT3DRenderer, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise { - const device = renderer.getRenderCache().device; - + private async spawnActorForRoom(renderer: OoT3DRenderer, dataHolder: DataHolder, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise { function fetchArchive(archivePath: string): Promise { - return renderer.modelCache.fetchArchive(`${pathBase}/actors/${archivePath}`); + return dataHolder.modelCache.fetchArchive(`${pathBase}/actors/${archivePath}`); } function buildModel(gar: ZAR.ZAR, modelPath: string, scale: number = 0.01): CmbInstance { - const cmbData = renderer.modelCache.getModel(renderer, gar, modelPath); - const cmbRenderer = new CmbInstance(renderer.getRenderCache(), renderer.textureHolder, cmbData); + const cmbData = dataHolder.modelCache.getModel(renderer, gar, modelPath); + const cmbRenderer = new CmbInstance(renderer.getRenderCache(), cmbData); cmbRenderer.animationController.fps = 20; cmbRenderer.setConstantColor(1, TransparentBlack); cmbRenderer.name = `${hexzero(actor.actorId, 4)} / ${hexzero(actor.variable, 4)} / ${modelPath}`; @@ -625,8 +623,9 @@ class SceneDesc implements Viewer.SceneDesc { function parseCMAB(gar: ZAR.ZAR, filename: string) { const cmab = CMAB.parse(CMB.Version.Majora, assertExists(ZAR.findFileData(gar, filename))); - renderer.textureHolder.addTextures(device, cmab.textures); - return cmab; + const cmabData = new CmabData(renderer.getRenderCache(), cmab); + dataHolder.cmabData.push(cmabData); + return cmabData; } function animFrame(frame: number): AnimationController { @@ -882,11 +881,10 @@ class SceneDesc implements Viewer.SceneDesc { dataFetcher.fetchData(path_info_zsi), ]); - const textureHolder = new CtrTextureHolder(); - context.destroyablePool.push(textureHolder); - const modelCache = new ModelCache(device, dataFetcher); - context.destroyablePool.push(modelCache); + + const dataHolder = new DataHolder(modelCache); + context.destroyablePool.push(dataHolder); const gar = ZAR.parse(maybeDecompress(zarBuffer)); @@ -897,7 +895,7 @@ class SceneDesc implements Viewer.SceneDesc { zsi.environmentSettings.push(new ZSIEnvironmentSettings()); } - const renderer = new OoT3DRenderer(device, textureHolder, zsi, modelCache); + const renderer = new OoT3DRenderer(device, zsi); const cache = renderer.getRenderCache(); context.destroyablePool.push(renderer); @@ -917,21 +915,22 @@ class SceneDesc implements Viewer.SceneDesc { assert(roomSetup.mesh !== null); const filename = roomZSINames[i].split('/').pop()!; - const roomRenderer = new RoomRenderer(cache, ZSI.Version.Majora, textureHolder, roomSetup.mesh, filename); + const roomRenderer = new RoomRenderer(cache, ZSI.Version.Majora, roomSetup.mesh, filename); roomRenderer.roomSetups = roomSetups; if (gar !== null) { const cmabFile = gar.files.find((file) => file.name.startsWith(`ROOM${i}/`) && file.name.endsWith('.cmab') && !file.name.endsWith('_t.cmab')); if (cmabFile) { - const cmab = CMAB.parse(CMB.Version.Majora, cmabFile.buffer); - textureHolder.addTextures(device, cmab.textures); - roomRenderer.bindCMAB(cmab); + const cmab = CMAB.parse(CMB.Version.Ocarina, cmabFile.buffer); + const cmabData = new CmabData(cache, cmab); + dataHolder.cmabData.push(cmabData); + roomRenderer.bindCMAB(cmabData); } } renderer.roomRenderers.push(roomRenderer); for (let j = 0; j < roomSetup.actors.length; j++) - this.spawnActorForRoom(renderer, roomRenderer, roomSetup.actors[j], j); + this.spawnActorForRoom(renderer, dataHolder, roomRenderer, roomSetup.actors[j], j); if (this.disabledRooms.includes(i)) roomRenderer.setVisible(false); diff --git a/src/OcarinaOfTime3D/oot3d_scenes.ts b/src/OcarinaOfTime3D/oot3d_scenes.ts index e1d35d3a2..11889bbf9 100644 --- a/src/OcarinaOfTime3D/oot3d_scenes.ts +++ b/src/OcarinaOfTime3D/oot3d_scenes.ts @@ -25,17 +25,16 @@ import { GfxRenderHelper } from '../gfx/render/GfxRenderHelper.js'; import { GfxRenderInstList } from '../gfx/render/GfxRenderInstManager.js'; import { assert, assertExists, hexzero } from '../util.js'; import { SceneGroup } from '../viewer.js'; -import { CmbData, CmbInstance, CtrTextureHolder, RoomRenderer, fillSceneParamsDataOnTemplate } from './render.js'; +import { CmabData, CmbData, CmbInstance, RoomRenderer, fillSceneParamsDataOnTemplate } from './render.js'; export const bindingLayouts: GfxBindingLayoutDescriptor[] = [{ numSamplers: 5, numUniformBuffers: 3, samplerEntries: [ - { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture0 - { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture1 - { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture2 - { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // LutTexture - { dimension: GfxTextureDimension.Cube, formatKind: GfxSamplerFormatKind.Float, }, // Cubemap - ]}]; - -const enum OoT3DPass { MAIN = 0x01, SKYBOX = 0x02 }; + { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture0 + { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture1 + { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // Texture2 + { dimension: GfxTextureDimension.n2D, formatKind: GfxSamplerFormatKind.Float, }, // LutTexture + { dimension: GfxTextureDimension.Cube, formatKind: GfxSamplerFormatKind.Float, }, // Cubemap +]}]; + export class OoT3DRenderer implements Viewer.SceneGfx { public skyRenderers: CmbInstance[] = []; public roomRenderers: RoomRenderer[] = []; @@ -45,7 +44,7 @@ export class OoT3DRenderer implements Viewer.SceneGfx { private renderInstListSky = new GfxRenderInstList(); private renderInstListMain = new GfxRenderInstList(); - constructor(device: GfxDevice, public textureHolder: CtrTextureHolder, public zsi: ZSI.ZSIScene, public modelCache: ModelCache) { + constructor(device: GfxDevice, public zsi: ZSI.ZSIScene) { this.renderHelper = new GfxRenderHelper(device); } @@ -288,7 +287,6 @@ export class ModelCache { if (p === undefined) { const cmbData = assertExists(ZAR.findFileData(zar, modelPath)); const cmb = CMB.parse(cmbData); - renderer.textureHolder.addTextures(this.renderCache.device, cmb.textures); p = new CmbData(this.renderCache, cmb); this.modelCache.set(modelPath, p); } @@ -820,13 +818,16 @@ function isAdultDungeon(scene: Scene) { const pathBase = `ZeldaOcarinaOfTime3D`; -class DataHolder { - constructor(public modelCache: ModelCache, public textureHolder: CtrTextureHolder) { +export class DataHolder { + public cmabData: CmabData[] = []; + + constructor(public modelCache: ModelCache) { } public destroy(device: GfxDevice): void { this.modelCache.destroy(device); - this.textureHolder.destroy(device); + for (let i = 0; i < this.cmabData.length; i++) + this.cmabData[i].destroy(device); } } @@ -834,16 +835,16 @@ class SceneDesc implements Viewer.SceneDesc { constructor(public id: string, public name: string, public environmentSettingsIndex: number = 0, public setupIndex: number = -1) { } - private async spawnActorForRoom(scene: Scene, renderer: OoT3DRenderer, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise { + private async spawnActorForRoom(scene: Scene, dataHolder: DataHolder, renderer: OoT3DRenderer, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise { const device = renderer.getRenderCache().device; function fetchArchive(archivePath: string): Promise { - return renderer.modelCache.fetchArchive(`${pathBase}/actor/${archivePath}`); + return dataHolder.modelCache.fetchArchive(`${pathBase}/actor/${archivePath}`); } function buildModel(zar: ZAR.ZAR, modelPath: string, scale: number = 0.01): CmbInstance { - const cmbData = renderer.modelCache.getModel(renderer, zar, modelPath); - const cmbRenderer = new CmbInstance(renderer.getRenderCache(), renderer.textureHolder, cmbData); + const cmbData = dataHolder.modelCache.getModel(renderer, zar, modelPath); + const cmbRenderer = new CmbInstance(renderer.getRenderCache(), cmbData); cmbRenderer.animationController.fps = 20; cmbRenderer.setConstantColor(1, TransparentBlack); cmbRenderer.name = `${hexzero(actor.actorId, 4)} / ${hexzero(actor.variable, 4)} / ${modelPath}`; @@ -859,8 +860,9 @@ class SceneDesc implements Viewer.SceneDesc { function parseCMAB(zar: ZAR.ZAR, filename: string) { const cmab = CMAB.parse(CMB.Version.Ocarina, assertExists(ZAR.findFileData(zar, filename))); - renderer.textureHolder.addTextures(device, cmab.textures); - return cmab; + const cmabData = new CmabData(renderer.getRenderCache(), cmab); + dataHolder.cmabData.push(cmabData); + return cmabData; } function animFrame(frame: number): AnimationController { @@ -1430,7 +1432,6 @@ class SceneDesc implements Viewer.SceneDesc { const b = buildModel(zar, `model/mjin_flash_model.cmb`, 1); const cmab = parseCMAB(zar, `misc/mjin_flash_model.cmab`); - renderer.textureHolder.addTextures(device, cmab.textures); b.bindCMAB(cmab, animFrame(whichPalFrame)); } else if (actor.actorId === ActorId.Bg_Ydan_Sp) { const zar = await fetchArchive(`zelda_ydan_objects.zar`); @@ -2382,19 +2383,25 @@ class SceneDesc implements Viewer.SceneDesc { } } - private spawnSkybox(renderer: OoT3DRenderer, zar: ZAR.ZAR, skyboxSettings: number, fogColor: Color): void { + private spawnSkybox(renderer: OoT3DRenderer, dataHolder: DataHolder, zar: ZAR.ZAR, skyboxSettings: number, fogColor: Color): void { // Attach the skybox to the first roomRenderer. const cache = renderer.getRenderCache(); function buildModel(zar: ZAR.ZAR, modelPath: string): CmbInstance { - const cmbData = renderer.modelCache.getModel(renderer, zar, modelPath); - const cmbRenderer = new CmbInstance(cache, renderer.textureHolder, cmbData); + const cmbData = dataHolder.modelCache.getModel(renderer, zar, modelPath); + const cmbRenderer = new CmbInstance(cache, cmbData); cmbRenderer.isSkybox = true; cmbRenderer.animationController.fps = 20; renderer.skyRenderers.push(cmbRenderer); return cmbRenderer; } - function parseCMAB(zar: ZAR.ZAR, filename: string) { return CMAB.parse(CMB.Version.Ocarina, assertExists(ZAR.findFileData(zar, filename))); } + + function parseCMAB(zar: ZAR.ZAR, filename: string) { + const cmab = CMAB.parse(CMB.Version.Ocarina, assertExists(ZAR.findFileData(zar, filename))); + const cmabData = new CmabData(cache, cmab); + dataHolder.cmabData.push(cmabData); + return cmabData; + } const whichSkybox = (skyboxSettings) & 0xFF; if (whichSkybox === 0x01) { @@ -2418,12 +2425,10 @@ class SceneDesc implements Viewer.SceneDesc { const dataHolder = await context.dataShare.ensureObject(`${pathBase}/DataHolder`, async () => { const modelCache = new ModelCache(device, dataFetcher); - const textureHolder = new CtrTextureHolder(); - return new DataHolder(modelCache, textureHolder); + return new DataHolder(modelCache); }); const modelCache = dataHolder.modelCache; - const textureHolder = dataHolder.textureHolder; const [zarBuffer, zsiBuffer] = await Promise.all([ modelCache.fetchFileData(path_zar, true), @@ -2436,7 +2441,7 @@ class SceneDesc implements Viewer.SceneDesc { const zsi = ZSI.parseScene(zsiBuffer); assert(zsi.rooms !== null); - const renderer = new OoT3DRenderer(device, textureHolder, zsi, modelCache); + const renderer = new OoT3DRenderer(device, zsi); const cache = renderer.getRenderCache(); context.destroyablePool.push(renderer); @@ -2465,28 +2470,29 @@ class SceneDesc implements Viewer.SceneDesc { assert(roomSetup.mesh !== null); const filename = roomZSINames[i].split('/').pop()!; - const roomRenderer = new RoomRenderer(cache, ZSI.Version.Ocarina, textureHolder, roomSetup.mesh, filename); + const roomRenderer = new RoomRenderer(cache, ZSI.Version.Ocarina, roomSetup.mesh, filename); roomRenderer.roomSetups = roomSetups; if (zar !== null) { const cmabFile = zar.files.find((file) => file.name.startsWith(`ROOM${i}\\`) && file.name.endsWith('.cmab') && !file.name.endsWith('_t.cmab')); if (cmabFile) { const cmab = CMAB.parse(CMB.Version.Ocarina, cmabFile.buffer); - textureHolder.addTextures(device, cmab.textures); - roomRenderer.bindCMAB(cmab); + const cmabData = new CmabData(cache, cmab); + dataHolder.cmabData.push(cmabData); + roomRenderer.bindCMAB(cmabData); } } renderer.roomRenderers.push(roomRenderer); for (let j = 0; j < roomSetup.actors.length; j++) - this.spawnActorForRoom(scene, renderer, roomRenderer, roomSetup.actors[j], j); + this.spawnActorForRoom(scene, dataHolder, renderer, roomRenderer, roomSetup.actors[j], j); } // We stick doors into the first roomRenderer to keep things simple. for (let j = 0; j < zsi.doorActors.length; j++) - this.spawnActorForRoom(scene, renderer, renderer.roomRenderers[0], zsi.doorActors[j], j); + this.spawnActorForRoom(scene, dataHolder, renderer, renderer.roomRenderers[0], zsi.doorActors[j], j); const skyboxZAR = modelCache.getArchive(`${pathBase}/kankyo/BlueSky.zar`); - this.spawnSkybox(renderer, skyboxZAR, zsi.skyboxSettings, renderer.zsi.environmentSettings[this.environmentSettingsIndex].fogColor); + this.spawnSkybox(renderer, dataHolder, skyboxZAR, zsi.skyboxSettings, renderer.zsi.environmentSettings[this.environmentSettingsIndex].fogColor); await modelCache.waitForLoad(); renderer.environmentSettingsIndex = this.environmentSettingsIndex; diff --git a/src/OcarinaOfTime3D/render.ts b/src/OcarinaOfTime3D/render.ts index 70769371b..fde0a95fe 100644 --- a/src/OcarinaOfTime3D/render.ts +++ b/src/OcarinaOfTime3D/render.ts @@ -6,7 +6,7 @@ import * as ZSI from './zsi.js'; import * as Viewer from '../viewer.js'; -import { mat4, vec3, vec4 } from 'gl-matrix'; +import { mat4, ReadonlyMat4, vec3, vec4 } from 'gl-matrix'; import AnimationController from '../AnimationController.js'; import ArrayBufferSlice from '../ArrayBufferSlice.js'; import { Camera, computeViewMatrix, computeViewMatrixSkybox } from '../Camera.js'; @@ -15,7 +15,6 @@ import { drawWorldSpaceLine, getDebugOverlayCanvas2D } from '../DebugJunk.js'; import { makeStaticDataBuffer } from '../gfx/helpers/BufferHelpers.js'; import { GfxShaderLibrary } from '../gfx/helpers/GfxShaderLibrary.js'; import { reverseDepthForDepthOffset } from '../gfx/helpers/ReversedDepthHelpers.js'; -import { convertToCanvas } from '../gfx/helpers/TextureConversionHelpers.js'; import { fillColor, fillMatrix4x3, fillMatrix4x4, fillVec4, fillVec4v } from '../gfx/helpers/UniformBufferHelpers.js'; import { GfxBuffer, GfxBufferUsage, GfxCompareMode, GfxDevice, GfxFormat, GfxIndexBufferDescriptor, GfxInputLayout, GfxInputLayoutBufferDescriptor, GfxMipFilterMode, GfxProgram, GfxSampler, GfxTexFilterMode, GfxTexture, GfxTextureDescriptor, GfxTextureDimension, GfxTextureUsage, GfxVertexAttributeDescriptor, GfxVertexBufferDescriptor, GfxVertexBufferFrequency, GfxWrapMode, makeTextureDescriptor2D } from '../gfx/platform/GfxPlatform.js'; import { getFormatByteSize, setFormatCompFlags } from '../gfx/platform/GfxPlatformFormat.js'; @@ -23,47 +22,10 @@ import { GfxRenderCache } from '../gfx/render/GfxRenderCache.js'; import { GfxRendererLayer, GfxRenderInst, GfxRenderInstManager, makeSortKey } from '../gfx/render/GfxRenderInstManager.js'; import { transformVec3Mat4w0 } from '../MathHelpers.js'; import { DeviceProgram } from '../Program.js'; -import { LoadedTexture, TextureHolder, TextureMapping } from '../TextureHolder.js'; +import { TextureMapping } from '../TextureHolder.js'; import { assert, nArray } from '../util.js'; import { ColorAnimType } from './cmab.js'; import { BumpMode, FresnelSelector, LightingConfig, LutInput, TexCoordConfig } from './cmb.js'; -import { getTextureFormatName } from './pica_texture.js'; - -function surfaceToCanvas(textureLevel: CMB.TextureLevel): HTMLCanvasElement { - const canvas = convertToCanvas(ArrayBufferSlice.fromView(textureLevel.pixels), textureLevel.width, textureLevel.height); - canvas.title = textureLevel.name; - return canvas; -} - -function textureToCanvas(texture: CMB.Texture): Viewer.Texture { - const surfaces = texture.dimension ? [] : texture.levels.map((textureLevel) => surfaceToCanvas(textureLevel)); - - const extraInfo = new Map(); - extraInfo.set('Format', getTextureFormatName(texture.format)); - - return { name: texture.name, surfaces, extraInfo }; -} - -export class CtrTextureHolder extends TextureHolder { - public loadTexture(device: GfxDevice, texture: CMB.Texture): LoadedTexture { - - const descriptor: GfxTextureDescriptor = { - width: texture.width, - height: texture.height, - pixelFormat: GfxFormat.U8_RGBA_NORM, - dimension: texture.dimension, - depthOrArrayLayers: texture.dimension === GfxTextureDimension.Cube ? 6 : 1, - numLevels: texture.levels.length, - usage: GfxTextureUsage.Sampled, - }; - - const gfxTexture = device.createTexture(descriptor); - device.setResourceName(gfxTexture, texture.name); - device.uploadTextureData(gfxTexture, 0, texture.levels.map((level) => level.pixels)); - const viewerTexture = textureToCanvas(texture); - return { gfxTexture, viewerTexture }; - } -} interface DMPMaterialHacks { texturesEnabled: boolean; @@ -301,7 +263,7 @@ uniform samplerCube u_Cubemap; const material = this.material; if(!material.isFragmentLightingEnabled) return ``; - + let S = ` vec3 t_LightVector = vec3(0.0); vec3 t_ReflValue = vec3(1.0); @@ -313,7 +275,7 @@ uniform samplerCube u_Cubemap; S += ` vec3 t_SurfNormal = ${material.bumpMode === BumpMode.AsBump ? bumpColor : `vec3(0.0, 0.0, 1.0);`} vec3 t_SurfTangent = ${material.bumpMode === BumpMode.AsTangent ? bumpColor : `vec3(1.0, 0.0, 0.0);`} - + bool isBumpRenormEnabled = ${material.isBumpRenormEnabled}; if (isBumpRenormEnabled) { t_SurfNormal.z = sqrt(max(1.0 - dot(t_SurfNormal.xy, t_SurfNormal.xy), 0.0)); @@ -340,21 +302,21 @@ uniform samplerCube u_Cubemap; const dist_atten = "1.0"; let d0_lut_value = "1.0"; let d1_lut_value = "1.0"; - + if(material.isGeoFactorEnabled){ S += ` t_GeoFactor = dot(t_HalfVector, t_HalfVector); t_GeoFactor = t_GeoFactor == 0.0 ? 0.0 : min(t_DotProduct / t_GeoFactor, 1.0); `; } - + if(material.isDist0Enabled && this.IsLUTSupported(MatLutType.Distribution0)) d0_lut_value = this.getLutInput(material.lutDist0); - + let specular_0 = `(${d0_lut_value} * u_SceneLights[i].Specular0.xyz)`; if(material.isGeo0Enabled) specular_0 = `(${specular_0} * t_GeoFactor)`; - + if(material.isReflectionEnabled){ if(this.IsLUTSupported(MatLutType.ReflectR)) S+= ` @@ -363,14 +325,14 @@ uniform samplerCube u_Cubemap; t_ReflValue.b = ${this.IsLUTSupported(MatLutType.ReflectB) ? this.getLutInput(material.lutReflecB) : `t_ReflValue.r`}; `; } - + if(material.isDist1Enabled && this.IsLUTSupported(MatLutType.Distribution1)) d1_lut_value = this.getLutInput(material.lutDist1); - + let specular_1 = `(${d1_lut_value} * t_ReflValue * u_SceneLights[i].Specular1.xyz)`; if(material.isGeo1Enabled) specular_1 = `(${specular_1} * t_GeoFactor)`; - + if (material.fresnelSelector !== FresnelSelector.No && this.IsLUTSupported(MatLutType.Fresnel)) { const value = this.getLutInput(material.lutFesnel); @@ -383,7 +345,7 @@ uniform samplerCube u_Cubemap; } S+=`\t\t}\n`; } - + S += ` t_FragPriColor.rgb += ((u_SceneLights[i].Diffuse.xyz * t_DotProduct) + u_SceneLights[i].Ambient.xyz) * ${dist_atten} * ${spot_atten}; t_FragSecColor.rgb += (${specular_0} + ${specular_1}) * t_ClampHighlights * ${dist_atten} * ${spot_atten}; @@ -472,7 +434,7 @@ void main() { #ifdef USE_UV t_ResultColor.rgba = vec4(v_TexCoord0.xy, 1.0, 1.0); #endif - + if(u_IsFogEnabled > 0.0 && u_RenderFog > 0.0) { //(M-1): Hack for now @@ -558,7 +520,7 @@ vec4 FullQuatCalcFallback(in vec4 t_temp0, in vec4 t_Normal, in vec4 t_temp1) { } vec4 CalcQuatFromTangent(in vec3 t_Tangent) { - + vec4 t_temp0 = vec4(normalize(cross(v_Normal, t_Tangent)), 0.0); vec4 t_temp1 = vec4(cross(t_temp0.xyz, v_Normal.xyz), t_temp0.z); vec4 t_Normal = vec4(v_Normal.xyz, t_temp0.x); @@ -615,7 +577,7 @@ vec3 CalcTextureCoordRaw(in int t_Idx) { // Cube env mapping. //vec3 t_Incident = normalize(vec3(t_Position.xy, -t_Position.z) - vec3(u_CameraPos.xy, -u_CameraPos.z)); //vec3 t_TexSrc = reflect(-t_Incident, v_Normal); - + return vec3(0.0); } else if (t_MappingMode == 3) { // Sphere env mapping. @@ -680,7 +642,7 @@ void main() { v_Depth = gl_Position.w; v_View = -t_ViewPosition; - + if (u_IsFragLighting > 0.0) { v_QuatNormal = u_HasTangent > 0.0 ? CalcQuatFromTangent(t_ViewTangent) : CalcQuatFromNormal(v_Normal); } @@ -752,7 +714,7 @@ class MaterialInstance { public environmentSettings = new ZSI.ZSIEnvironmentSettings; - constructor(cache: GfxRenderCache, lutTexure: GfxTexture | null, public cmb: CMB.CMB, public material: CMB.Material) { + constructor(cache: GfxRenderCache, lutTexure: GfxTexture | null, public cmbData: CmbData, public material: CMB.Material) { this.diffuseColor = colorNewCopy(this.material.diffuseColor); this.ambientColor = colorNewCopy(this.material.ambientColor); this.specular0Color = colorNewCopy(this.material.specular0Color); @@ -781,7 +743,8 @@ class MaterialInstance { }); this.gfxSamplers.push(gfxSampler); - if(i == 0 && cmb.textures[binding.textureIdx].dimension === GfxTextureDimension.Cube) + const cmb = this.cmbData.cmb; + if (i == 0 && cmb.textures[binding.textureIdx].dimension === GfxTextureDimension.Cube) this.textureMappings[4].gfxSampler = gfxSampler; else this.textureMappings[i].gfxSampler = gfxSampler; @@ -869,7 +832,7 @@ class MaterialInstance { return (textureCoordinator.mappingMethod << 12) | (textureCoordinator.sourceCoordinate << 8); } - public setOnRenderInst(cache: GfxRenderCache, template: GfxRenderInst, textureHolder: CtrTextureHolder, viewMatrix: mat4): void { + public setOnRenderInst(cache: GfxRenderCache, template: GfxRenderInst, viewMatrix: ReadonlyMat4): void { let offs = template.allocateUniformBuffer(DMPProgram.ub_MaterialParams, 4*4 + 4*5*3 + 4*2 + 4*6 + 4*3*3 + 4); const layer = this.material.isTransparent ? GfxRendererLayer.TRANSLUCENT : GfxRendererLayer.OPAQUE; template.sortKey = makeSortKey(layer + this.material.renderLayer); @@ -896,14 +859,14 @@ class MaterialInstance { colorClamp(scratchColor, scratchColor, 0, 1); offs += fillColor(mapped, offs, scratchColor); - - if(this.material.isFragmentLightingEnabled) { + + if (this.material.isFragmentLightingEnabled) { for (let i = 0; i < 3; i++) { const light = this.environmentSettings.lights[i]; - + colorMult(scratchColor, light.ambient, this.ambientColor); offs += fillColor(mapped, offs, scratchColor); - + colorMult(scratchColor, light.diffuse, this.diffuseColor); offs += fillColor(mapped, offs, scratchColor); @@ -941,13 +904,16 @@ class MaterialInstance { for (let i = 0; i < 3; i++) { const binding = this.material.textureBindings[i]; if (binding.textureIdx >= 0) { + const texture = this.cmbData.cmb.textures[binding.textureIdx]; + const dst = this.textureMappings[texture.dimension === GfxTextureDimension.Cube ? 4 : i]; + if (this.texturePaletteAnimators[i]) { - this.texturePaletteAnimators[i].fillTextureMapping(textureHolder, this.textureMappings[i]); + dst.gfxTexture = this.texturePaletteAnimators[i].getTexture(); } else { - const texture = this.cmb.textures[binding.textureIdx]; - textureHolder.fillTextureMapping(this.textureMappings[texture.dimension === GfxTextureDimension.Cube ? 4 : i], texture.name); + dst.gfxTexture = this.cmbData.textureData[binding.textureIdx].gfxTexture; } + assert(dst.gfxTexture !== undefined); scratchVec4[i] = this.packTexCoordParams(this.material.textureCoordinators[i]); this.calcTexMtx(scratchMatrix, i, this.material.textureCoordinators[i]); } else { @@ -966,7 +932,8 @@ class MaterialInstance { template.setSamplerBindingsFromTextureMappings(this.textureMappings); } - public bindCMAB(cmab: CMAB.CMAB, animationController: AnimationController): void { + public bindCMAB(cmabData: CmabData, animationController: AnimationController): void { + const cmab = cmabData.cmab; for (let i = 0; i < cmab.animEntries.length; i++) { const animEntry = cmab.animEntries[i]; if (animEntry.materialIndex !== this.material.index) @@ -979,7 +946,7 @@ class MaterialInstance { animEntry.animationType === CMAB.AnimationType.EmissionColor || animEntry.animationType === CMAB.AnimationType.AmbientColor) { this.colorAnimators[animEntry.channelIndex] = new CMAB.ColorAnimator(animationController, cmab, animEntry); } else if (animEntry.animationType === CMAB.AnimationType.TexturePalette) { - this.texturePaletteAnimators[animEntry.channelIndex] = new CMAB.TexturePaletteAnimator(animationController, cmab, animEntry); + this.texturePaletteAnimators[animEntry.channelIndex] = new CMAB.TexturePaletteAnimator(animationController, cmabData, animEntry); } } } @@ -1086,10 +1053,10 @@ class SepdData { vertexAttributeDescriptors.push({ location, format, bufferIndex, bufferByteOffset: 0 }); }; - loadVertexAttrib(DMPProgram.a_Position, GfxFormat.F32_RGB, vatr.position, sepd.position); - loadVertexAttrib(DMPProgram.a_Normal, GfxFormat.F32_RGB, vatr.normal, sepd.normal); + loadVertexAttrib(DMPProgram.a_Position, GfxFormat.F32_RGB, vatr.position, sepd.position); + loadVertexAttrib(DMPProgram.a_Normal, GfxFormat.F32_RGB, vatr.normal, sepd.normal); loadVertexAttrib(DMPProgram.a_Tangent, GfxFormat.F32_RGB, sepd.hasTangents ? vatr.tangent : null, sepd.tangent); - loadVertexAttrib(DMPProgram.a_Color, GfxFormat.F32_RGBA, vatr.color, sepd.color); + loadVertexAttrib(DMPProgram.a_Color, GfxFormat.F32_RGBA, vatr.color, sepd.color); loadVertexAttrib(DMPProgram.a_TexCoord0, GfxFormat.F32_RG, vatr.texCoord0, sepd.texCoord0); loadVertexAttrib(DMPProgram.a_TexCoord1, GfxFormat.F32_RG, vatr.texCoord1, sepd.texCoord1); loadVertexAttrib(DMPProgram.a_TexCoord2, GfxFormat.F32_RG, vatr.texCoord2, sepd.texCoord2); @@ -1140,7 +1107,7 @@ class ShapeInstance { constructor(private sepdData: SepdData, private materialInstance: MaterialInstance) { } - public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, textureHolder: CtrTextureHolder, boneMatrices: mat4[], viewMatrix: mat4, inverseBindPoseMatrices: mat4[]): void { + public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, boneMatrices: ReadonlyMat4[], viewMatrix: ReadonlyMat4, inverseBindPoseMatrices: ReadonlyMat4[]): void { if (!this.visible || !this.materialInstance.visible) return; @@ -1148,7 +1115,7 @@ class ShapeInstance { const materialTemplate = renderInstManager.pushTemplate(); materialTemplate.setVertexInput(this.sepdData.inputLayout, this.sepdData.vertexBufferDescriptors, this.sepdData.indexBufferDescriptor); - this.materialInstance.setOnRenderInst(renderInstManager.gfxRenderCache, materialTemplate, textureHolder, viewMatrix); + this.materialInstance.setOnRenderInst(renderInstManager.gfxRenderCache, materialTemplate, viewMatrix); for (let i = 0; i < this.sepdData.sepd.prms.length; i++) { const prmsData = this.sepdData.prmsData[i]; @@ -1189,15 +1156,60 @@ class ShapeInstance { } } +class TextureData { + public gfxTexture: GfxTexture; + + constructor(cache: GfxRenderCache, texture: CMB.Texture) { + assert(texture.levels.length > 0); + const device = cache.device; + + const descriptor: GfxTextureDescriptor = { + width: texture.width, + height: texture.height, + pixelFormat: GfxFormat.U8_RGBA_NORM, + dimension: texture.dimension, + depthOrArrayLayers: texture.dimension === GfxTextureDimension.Cube ? 6 : 1, + numLevels: texture.levels.length, + usage: GfxTextureUsage.Sampled, + }; + + this.gfxTexture = device.createTexture(descriptor); + device.setResourceName(this.gfxTexture, texture.name); + device.uploadTextureData(this.gfxTexture, 0, texture.levels.map((level) => level.pixels)); + } + + public destroy(device: GfxDevice): void { + device.destroyTexture(this.gfxTexture); + } +} + +export class CmabData { + public textureData: TextureData[] = []; + + constructor(cache: GfxRenderCache, public cmab: CMAB.CMAB) { + for (let i = 0; i < this.cmab.textures.length; i++) + this.textureData.push(new TextureData(cache, this.cmab.textures[i])); + } + + public destroy(device: GfxDevice): void { + for (let i = 0; i < this.textureData.length; i++) + this.textureData[i].destroy(device); + } +} + export class CmbData { + public textureData: TextureData[] = []; public sepdData: SepdData[] = []; public inverseBindPoseMatrices: mat4[] = []; constructor(cache: GfxRenderCache, public cmb: CMB.CMB) { const vatrChunk = cmb.vatrChunk; + for (let i = 0; i < this.cmb.textures.length; i++) + this.textureData.push(new TextureData(cache, this.cmb.textures[i])); + for (let i = 0; i < this.cmb.sepds.length; i++) - this.sepdData[i] = new SepdData(cache, cmb.indexBuffer, vatrChunk, this.cmb.sepds[i]); + this.sepdData.push(new SepdData(cache, cmb.indexBuffer, vatrChunk, this.cmb.sepds[i])); const tempBones = nArray(cmb.bones.length, () => mat4.create()); for (let i = 0; i < cmb.bones.length; i++) { @@ -1213,6 +1225,8 @@ export class CmbData { } public destroy(device: GfxDevice): void { + for (let i = 0; i < this.textureData.length; i++) + this.textureData[i].destroy(device); for (let i = 0; i < this.sepdData.length; i++) this.sepdData[i].destroy(device); } @@ -1226,7 +1240,7 @@ export class CmbInstance { public visible: boolean = true; public materialInstances: MaterialInstance[] = []; public shapeInstances: ShapeInstance[] = []; - private gfxLutTexture: GfxTexture | null = null; + private lutTexture: GfxTexture | null = null; public csab: CSAB.CSAB | null = null; public debugBones: boolean = false; @@ -1235,16 +1249,16 @@ export class CmbInstance { public isSkybox = false; public passMask: number = 1; - constructor(cache: GfxRenderCache, public textureHolder: CtrTextureHolder, public cmbData: CmbData, public name: string = '') { - if(this.cmbData.cmb.lutTexture) { + constructor(cache: GfxRenderCache, public cmbData: CmbData, public name: string = '') { + if (this.cmbData.cmb.lutTexture) { const texture = this.cmbData.cmb.lutTexture; - this.gfxLutTexture = cache.device.createTexture(makeTextureDescriptor2D(GfxFormat.U8_R_NORM, texture.width, texture.height, 1)); - cache.device.uploadTextureData(this.gfxLutTexture, 0, texture.levels.map((level) => level.pixels)); + this.lutTexture = cache.device.createTexture(makeTextureDescriptor2D(GfxFormat.U8_R_NORM, texture.width, texture.height, 1)); + cache.device.uploadTextureData(this.lutTexture, 0, texture.levels.map((level) => level.pixels)); } - + for (let i = 0; i < this.cmbData.cmb.materials.length; i++) - this.materialInstances.push(new MaterialInstance(cache, this.gfxLutTexture, this.cmbData.cmb, this.cmbData.cmb.materials[i])); - + this.materialInstances.push(new MaterialInstance(cache, this.lutTexture, this.cmbData, this.cmbData.cmb.materials[i])); + for (let i = 0; i < this.cmbData.cmb.meshs.length; i++) { const mesh = this.cmbData.cmb.meshs[i]; this.shapeInstances.push(new ShapeInstance(this.cmbData.sepdData[mesh.sepdIdx], this.materialInstances[mesh.matsIdx])); @@ -1342,7 +1356,7 @@ export class CmbInstance { } for (let i = 0; i < this.shapeInstances.length; i++) - this.shapeInstances[i].prepareToRender(device, renderInstManager, this.textureHolder, this.boneMatrices, scratchViewMatrix, this.cmbData.inverseBindPoseMatrices); + this.shapeInstances[i].prepareToRender(device, renderInstManager, this.boneMatrices, scratchViewMatrix, this.cmbData.inverseBindPoseMatrices); } public setVisible(visible: boolean): void { @@ -1350,8 +1364,8 @@ export class CmbInstance { } public destroy(device: GfxDevice): void { - if(this.gfxLutTexture) - device.destroyTexture(this.gfxLutTexture); + if(this.lutTexture) + device.destroyTexture(this.lutTexture); for (let i = 0; i < this.materialInstances.length; i++) this.materialInstances[i].destroy(device); @@ -1363,7 +1377,7 @@ export class CmbInstance { this.csab = csab; } - public bindCMAB(cmab: CMAB.CMAB, animationController = this.animationController): void { + public bindCMAB(cmab: CmabData, animationController = this.animationController): void { for (let i = 0; i < this.materialInstances.length; i++) this.materialInstances[i].bindCMAB(cmab, animationController); } @@ -1378,27 +1392,25 @@ export class RoomRenderer { public objectRenderers: CmbInstance[] = []; public roomSetups: ZSI.ZSIRoomSetup[] = []; - constructor(cache: GfxRenderCache, public version: ZSI.Version, public textureHolder: CtrTextureHolder, public mesh: ZSI.Mesh, public name: string) { + constructor(cache: GfxRenderCache, public version: ZSI.Version, public mesh: ZSI.Mesh, public name: string) { const device = cache.device; if (mesh.opaque !== null) { - textureHolder.addTextures(device, mesh.opaque.textures); this.opaqueData = new CmbData(cache, mesh.opaque); - this.opaqueMesh = new CmbInstance(cache, textureHolder, this.opaqueData, `${name} Opaque`); + this.opaqueMesh = new CmbInstance(cache, this.opaqueData, `${name} Opaque`); this.opaqueMesh.animationController.fps = 20; this.opaqueMesh.setConstantColor(1, TransparentBlack); } if (mesh.transparent !== null) { - textureHolder.addTextures(device, mesh.transparent.textures); this.transparentData = new CmbData(cache, mesh.transparent); - this.transparentMesh = new CmbInstance(cache, textureHolder, this.transparentData, `${name} Transparent`); + this.transparentMesh = new CmbInstance(cache, this.transparentData, `${name} Transparent`); this.transparentMesh.animationController.fps = 20; this.transparentMesh.setConstantColor(1, TransparentBlack); } } - public bindCMAB(cmab: CMAB.CMAB): void { + public bindCMAB(cmab: CmabData): void { if (this.opaqueMesh !== null) this.opaqueMesh.bindCMAB(cmab); if (this.transparentMesh !== null) @@ -1460,8 +1472,7 @@ export class RoomRenderer { } public setEnvironmentSettings(environmentSettings: ZSI.ZSIEnvironmentSettings): void { - - //(M-1): Temporay hack until I get kankyo implemented + //(M-1): Temporary hack until I get kankyo implemented const envSettingsRoom = new ZSI.ZSIEnvironmentSettings(); envSettingsRoom.copy(environmentSettings); diff --git a/src/OcarinaOfTime3D/scenes.ts b/src/OcarinaOfTime3D/scenes.ts index 905c3f92f..e81b87c3c 100644 --- a/src/OcarinaOfTime3D/scenes.ts +++ b/src/OcarinaOfTime3D/scenes.ts @@ -1,7 +1,6 @@ import * as CMAB from './cmab.js'; import * as CSAB from './csab.js'; -import * as CTXB from './ctxb.js'; import * as CMB from './cmb.js'; import * as ZAR from './zar.js'; import * as LzS from './LzS.js'; @@ -9,7 +8,7 @@ import * as LzS from './LzS.js'; import * as Viewer from '../viewer.js'; import * as UI from '../ui.js'; -import { CtrTextureHolder, CmbInstance, CmbData, fillSceneParamsDataOnTemplate } from "./render.js"; +import { CmbInstance, CmbData, fillSceneParamsDataOnTemplate, CmabData } from "./render.js"; import { GfxDevice} from "../gfx/platform/GfxPlatform.js"; import ArrayBufferSlice from '../ArrayBufferSlice.js'; import { makeBackbufferDescSimple, standardFullClearRenderPassDescriptor } from '../gfx/helpers/RenderGraphHelpers.js'; @@ -22,43 +21,18 @@ import { bindingLayouts } from './oot3d_scenes.js'; import { ZSIEnvironmentSettings } from './zsi.js'; import { vec3 } from 'gl-matrix'; -export class GrezzoTextureHolder extends CtrTextureHolder { - public override findTextureEntryIndex(name: string): number { - let i: number = -1; - - i = this.searchTextureEntryIndex(name); - if (i >= 0) return i; - - i = this.searchTextureEntryIndex(`${name.split('/')[2]}.ctxb`); - if (i >= 0) return i; - - return i; - } - - public addCMB(device: GfxDevice, cmb: CMB.CMB): void { - this.addTextures(device, cmb.textures.filter((tex) => tex.levels.length > 0)); - } - - public addCTXB(device: GfxDevice, ctxb: CTXB.CTXB): void { - this.addTextures(device, ctxb.textures.map((texture) => { - const basename = texture.name.split('/')[2]; - const name = `${basename}.ctxb`; - return { ...texture, name }; - })); - } -} - export class MultiCmbScene implements Viewer.SceneGfx { private renderHelper: GfxRenderHelper; private renderInstListMain = new GfxRenderInstList(); private renderInstListSky = new GfxRenderInstList(); public cmbData: CmbData[] = []; + public cmabData: CmabData[] = []; public cmbRenderers: CmbInstance[] = []; public skyRenderers: CmbInstance[] = []; public cmab: CMAB.CMAB[] = []; public csab: CSAB.CSAB[] = []; - constructor(device: GfxDevice, public textureHolder: CtrTextureHolder) { + constructor(device: GfxDevice) { this.renderHelper = new GfxRenderHelper(device); } @@ -125,11 +99,12 @@ export class MultiCmbScene implements Viewer.SceneGfx { public destroy(device: GfxDevice): void { this.renderHelper.destroy(); - this.textureHolder.destroy(device); for (let i = 0; i < this.cmbRenderers.length; i++) this.cmbRenderers[i].destroy(device); for (let i = 0; i < this.cmbData.length; i++) this.cmbData[i].destroy(device); + for (let i = 0; i < this.cmabData.length; i++) + this.cmabData[i].destroy(device); } public createPanels(): UI.Panel[] { @@ -192,8 +167,8 @@ class SometimesMultiSelect extends UI.ScrollSelect { class ArchiveCmbScene implements Viewer.SceneGfx { private renderHelper: GfxRenderHelper; private renderInstListMain = new GfxRenderInstList(); - public textureHolder = new GrezzoTextureHolder(); public cmbData: CmbData[] = []; + public cmabData: CmabData[] = []; public cmbRenderers: CmbInstance[] = []; constructor(private device: GfxDevice, private archive: ZAR.ZAR) { @@ -243,7 +218,6 @@ class ArchiveCmbScene implements Viewer.SceneGfx { public destroy(device: GfxDevice): void { this.renderHelper.destroy(); - this.textureHolder.destroy(device); for (let i = 0; i < this.cmbRenderers.length; i++) this.cmbRenderers[i].destroy(device); for (let i = 0; i < this.cmbData.length; i++) @@ -261,17 +235,18 @@ class ArchiveCmbScene implements Viewer.SceneGfx { this.cmbRenderers[i].destroy(device); for (let i = 0; i < this.cmbData.length; i++) this.cmbData[i].destroy(device); + for (let i = 0; i < this.cmabData.length; i++) + this.cmabData[i].destroy(device); this.cmbRenderers = []; this.cmbData = []; + this.cmabData = []; const cache = this.renderHelper.renderCache; const cmb = CMB.parse(file.buffer); const cmbData = new CmbData(cache, cmb); - this.textureHolder.destroy(device); - this.textureHolder.addTextures(device, cmb.textures); this.cmbData.push(cmbData); - const cmbRenderer = new CmbInstance(cache, this.textureHolder, cmbData, file.name); + const cmbRenderer = new CmbInstance(cache, cmbData, file.name); if (cmbData.cmb.version === CMB.Version.Ocarina) { const envSettings = new ZSIEnvironmentSettings(); @@ -324,11 +299,10 @@ class ArchiveCmbScene implements Viewer.SceneGfx { return false; const cmab = CMAB.parse(this.archive.version, file.buffer); - this.textureHolder.addTextures(this.device, cmab.textures); + const cmabData = new CmabData(this.renderHelper.renderCache, cmab); - for (let i = 0; i < this.cmbRenderers.length; i++){ - this.cmbRenderers[i].bindCMAB(cmab); - } + for (let i = 0; i < this.cmbRenderers.length; i++) + this.cmbRenderers[i].bindCMAB(cmabData); // Deselect all other CMAB files except ours. for (let j = 0; j < this.archive.files.length; j++) diff --git a/src/Scenes_Test.ts b/src/Scenes_Test.ts index d9a5cf5e8..9b3ad2359 100644 --- a/src/Scenes_Test.ts +++ b/src/Scenes_Test.ts @@ -5,7 +5,6 @@ import { SceneContext } from "./SceneBase.js"; import { createBasicRRESRendererFromBRRES } from "./rres/scenes.js"; import * as H3D from "./Common/CTR_H3D/H3D.js"; -import { CtrTextureHolder } from "./OcarinaOfTime3D/render.js"; import * as NARC from "./nns_g3d/narc.js"; import { makeBackbufferDescSimple, standardFullClearRenderPassDescriptor } from "./gfx/helpers/RenderGraphHelpers.js"; import { GfxRenderHelper } from "./gfx/render/GfxRenderHelper.js"; @@ -68,23 +67,6 @@ class BasicRRESSceneDesc implements Viewer.SceneDesc { } } -class H3DScene extends EmptyScene { - public textureHolder = new CtrTextureHolder(); -} - -class H3DSceneDesc implements Viewer.SceneDesc { - constructor(public dataPath: string, public id: string = dataPath, public name: string = dataPath) {} - - public async createScene(device: GfxDevice, context: SceneContext): Promise { - const dataFetcher = context.dataFetcher; - const data = await dataFetcher.fetchData(this.dataPath); - const h3d = H3D.parse(data); - const renderer = new H3DScene(); - renderer.textureHolder.addTextures(device, h3d.textures); - return renderer; - } -} - class NARCSceneDesc implements Viewer.SceneDesc { constructor(public dataPath: string, public id: string = dataPath, public name: string = dataPath) {} @@ -100,7 +82,6 @@ class NARCSceneDesc implements Viewer.SceneDesc { const sceneDescs = [ new EmptyClearSceneDesc('EmptyClearScene'), new BasicRRESSceneDesc('test/dthro_cmn1.brres'), - new H3DSceneDesc('test/cave_Common.bch'), new NARCSceneDesc('test/land_data.narc'), ];