From 3a327cfe1c2234980a248aabd1bdfa0901e06ce2 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sat, 26 Nov 2022 18:45:49 -0800 Subject: [PATCH 01/38] Added warning and error if CI formats not compatible, moved setting format from DPSetTextureLUT for each texture to part of othermode --- .gitignore | 2 + fast64_internal/f3d/f3d_material.py | 14 +++++ fast64_internal/f3d/f3d_parser.py | 79 +++++++++++------------- fast64_internal/f3d/f3d_writer.py | 62 +++++++++++-------- fast64_internal/oot/oot_model_classes.py | 1 + 5 files changed, 88 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index ec40dd1d2..61ce85402 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__/ .vscode *.blend1 /.venv +fast64_updater/ + diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 62cbb07f6..d2c500657 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -752,6 +752,18 @@ def drawVertexColorNotice(self, layout): def drawShadeAlphaNotice(self, layout): layout.box().column().label(text='There must be a vertex color layer called "Alpha".', icon="IMAGE_ALPHA") + def checkDrawMixedCIWarning(self, layout, useDict, f3dMat): + useTex0 = useDict["Texture 0"] and f3dMat.tex0.tex_set + useTex1 = useDict["Texture 1"] and f3dMat.tex1.tex_set + if not useTex0 or not useTex1: + return + isTex0CI = f3dMat.tex0.tex_format[:2] == "CI" + isTex1CI = f3dMat.tex1.tex_format[:2] == "CI" + if isTex0CI != isTex1CI: + layout.box().column().label(text="Can't have one CI tex and one non-CI.", icon="ERROR") + if isTex0CI and isTex1CI and (f3dMat.tex0.ci_format != f3dMat.tex1.ci_format): + layout.box().column().label(text="Two CI textures must use the same CI format.", icon="ERROR") + def draw_simple(self, f3dMat, material, layout, context): self.ui_uvCheck(layout, context) @@ -765,6 +777,7 @@ def draw_simple(self, f3dMat, material, layout, context): useMultitexture = useDict["Texture 0"] and useDict["Texture 1"] and f3dMat.tex0.tex_set and f3dMat.tex1.tex_set + self.checkDrawMixedCIWarning(inputCol, useDict, f3dMat) canUseLargeTextures = material.mat_ver > 3 and material.f3d_mat.use_large_textures if useDict["Texture 0"] and f3dMat.tex0.tex_set: ui_image(canUseLargeTextures, inputCol, f3dMat.tex0, "Texture 0", False) @@ -866,6 +879,7 @@ def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): useMultitexture = useDict["Texture 0"] and useDict["Texture 1"] + self.checkDrawMixedCIWarning(inputCol, useDict, f3dMat) canUseLargeTextures = material.mat_ver > 3 and material.f3d_mat.use_large_textures if useDict["Texture 0"]: ui_image(canUseLargeTextures, inputCol, f3dMat.tex0, "Texture 0", True) diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index 7afd133cc..4cf30f714 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -1011,17 +1011,21 @@ def setCombineMode(self, command): self.setCombineLerp(lerp0, lerp1) - def setTLUTMode(self, index, value): + def setTLUTMode(self, flags): mat = self.mat() - texProp = getattr(mat, "tex" + str(index)) - bitData = math_eval(value, self.f3d) - if value == self.f3d.G_TT_NONE: - if texProp.tex_format[:2] == "CI": - texProp.tex_format = "RGBA16" - elif value == self.f3d.G_TT_IA16: - texProp.ci_format = "IA16" + if not isinstance(flags, int): + flags = math_eval(flags, self.f3d) else: - texProp.ci_format = "RGBA16" + flags &= 3 << self.f3d.G_MDSFT_TEXTLUT + for index in range(2): + texProp = getattr(mat, "tex" + str(index)) + if flags == self.f3d.G_TT_IA16: + texProp.ci_format = "IA16" + elif flags == self.f3d.G_TT_RGBA16: + texProp.ci_format = "RGBA16" + else: # self.f3d.G_TT_NONE or the unsupported value of 1 + if texProp.tex_format[:2] == "CI": + texProp.tex_format = "RGBA16" def setOtherModeFlags(self, command): mat = self.mat() @@ -1031,11 +1035,25 @@ def setOtherModeFlags(self, command): else: self.setOtherModeFlagsL(command) - def setOtherModeFlagsH(self, command): + def setFlagsAttrs(self, command, database): + mat = self.mat() + flags = math_eval(command.params[3], self.f3d) + shift = math_eval(command.params[1], self.f3d) + mask = math_eval(command.params[2], self.f3d) + for field, fieldData in database.items(): + fieldShift = getattr(self.f3d, field) + if fieldShift >= shift and fieldShift < shift + mask: + if isinstance(fieldData, list): + value = (flags >> fieldShift) & ((1 << int(ceil(math.log(len(fieldData), 2)))) - 1) + setattr(mat.rdp_settings, field.lower(), fieldData[value]) + else: + fieldData(flags) + + def setOtherModeFlagsH(self, command): otherModeH = { "G_MDSFT_ALPHADITHER": ["G_AD_PATTERN", "G_AD_NOTPATTERN", "G_AD_NOISE", "G_AD_DISABLE"], - "G_MDSFT_RGBDITHER": ["G_CD_MAGICSQ", "G_CD_BAYER", "NOISE"], + "G_MDSFT_RGBDITHER": ["G_CD_MAGICSQ", "G_CD_BAYER", "G_CD_NOISE", "G_CD_DISABLE"], "G_MDSFT_COMBKEY": ["G_CK_NONE", "G_CK_KEY"], "G_MDSFT_TEXTCONV": [ "G_TC_CONV", @@ -1047,26 +1065,15 @@ def setOtherModeFlagsH(self, command): "G_TC_FILT", ], "G_MDSFT_TEXTFILT": ["G_TF_POINT", "G_TF_POINT", "G_TF_BILERP", "G_TF_AVERAGE"], + "G_MDSFT_TEXTLUT": self.setTLUTMode, "G_MDSFT_TEXTLOD": ["G_TL_TILE", "G_TL_LOD"], "G_MDSFT_TEXTDETAIL": ["G_TD_CLAMP", "G_TD_SHARPEN", "G_TD_DETAIL"], "G_MDSFT_TEXTPERSP": ["G_TP_NONE", "G_TP_PERSP"], "G_MDSFT_CYCLETYPE": ["G_CYC_1CYCLE", "G_CYC_2CYCLE", "G_CYC_COPY", "G_CYC_FILL"], - "G_MDSFT_COLORDITHER": ["G_CD_MAGICSQ", "G_CD_BAYER", "G_CD_NOISE"], + "G_MDSFT_COLORDITHER": ["G_CD_DISABLE", "G_CD_ENABLE"], "G_MDSFT_PIPELINE": ["G_PM_NPRIMITIVE", "G_PM_1PRIMITIVE"], } - mat = self.mat() - flags = math_eval(command.params[3], self.f3d) - shift = math_eval(command.params[1], self.f3d) - mask = math_eval(command.params[2], self.f3d) - - for field, fieldData in otherModeH.items(): - fieldShift = getattr(self.f3d, field) - if fieldShift >= shift and fieldShift < shift + mask: - setattr( - mat.rdp_settings, - field.lower(), - fieldData[(flags >> fieldShift) & ((1 << int(ceil(math.log(len(fieldData), 2)))) - 1)], - ) + self.setFlagsAttrs(command, otherModeH) # This only handles commonly used render mode presets (with macros), # and no render modes at all with raw bit data. @@ -1074,24 +1081,9 @@ def setOtherModeFlagsL(self, command): otherModeL = { "G_MDSFT_ALPHACOMPARE": ["G_AC_NONE", "G_AC_THRESHOLD", "G_AC_THRESHOLD", "G_AC_DITHER"], "G_MDSFT_ZSRCSEL": ["G_ZS_PIXEL", "G_ZS_PRIM"], + "G_MDSFT_RENDERMODE": self.setRenderMode, } - - mat = self.mat() - flags = math_eval(command.params[3], self.f3d) - shift = math_eval(command.params[1], self.f3d) - mask = math_eval(command.params[2], self.f3d) - - for field, fieldData in otherModeL.items(): - fieldShift = getattr(self.f3d, field) - if fieldShift >= shift and fieldShift < shift + mask: - setattr( - mat.rdp_settings, - field.lower(), - fieldData[(flags >> fieldShift) & ((1 << int(ceil(math.log(len(fieldData), 2)))) - 1)], - ) - - if self.f3d.G_MDSFT_RENDERMODE >= shift and self.f3d.G_MDSFT_RENDERMODE < shift + mask: - self.setRenderMode(flags) + self.setFlagsAttrs(command, otherModeL) def setRenderMode(self, flags): mat = self.mat() @@ -1605,8 +1597,7 @@ def processCommands(self, dlData, dlName, dlCommands): elif command.name == "gsDPSetTextureLOD": mat.rdp_settings.g_mdsft_textlod = command.params[0] elif command.name == "gsDPSetTextureLUT": - self.setTLUTMode(0, command.params[0]) - self.setTLUTMode(1, command.params[0]) + self.setTLUTMode(command.params[0]) elif command.name == "gsDPSetTextureFilter": mat.rdp_settings.g_mdsft_text_filt = command.params[0] elif command.name == "gsDPSetTextureConvert": diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index c7b688db6..de10766e7 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1549,6 +1549,33 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): useDict = all_combiner_uses(f3dMat) + # Get tlut info for othermode + useTex0 = useDict["Texture 0"] and f3dMat.tex0.tex_set + useTex1 = useDict["Texture 1"] and f3dMat.tex1.tex_set + isTex0CI = f3dMat.tex0.tex_format[:2] == "CI" + isTex1CI = f3dMat.tex1.tex_format[:2] == "CI" + tex0CIFmt = f3dMat.tex0.ci_format + tex1CIFmt = f3dMat.tex1.ci_format + + if useTex0 and useTex1: + if isTex0CI != isTex1CI: + raise PluginError( + "In material " + + material.name + + ": N64 does not support CI + non-CI texture. " + + "Must be both CI or neither CI." + ) + if tex0CIFmt != tex1CIFmt: + raise PluginError( + "In material " + + material.name + + ": Both CI textures must use the same CI format." + ) + + isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) + ci_format = tex0CIFmt if useTex0 else tex1CIFmt + tlut = "G_TT_NONE" if not isCI else ("G_TT_" + ci_format) + if drawLayer is not None: defaultRM = fModel.getRenderMode(drawLayer) else: @@ -1559,7 +1586,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): saveGeoModeDefinitionF3DEX2(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) else: saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) - saveOtherModeHDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod) + saveOtherModeHDefinition(fMaterial, f3dMat.rdp_settings, tlut, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1579,21 +1606,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): nextTmem = 0 useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures - - useTex0 = useDict["Texture 0"] and f3dMat.tex0.tex_set - isTex0CI = f3dMat.tex0.tex_format[:2] == "CI" - useTex1 = useDict["Texture 1"] and f3dMat.tex1.tex_set - isTex1CI = f3dMat.tex1.tex_format[:2] == "CI" - useSharedCIPalette = ( useTex0 and useTex1 - and isTex0CI - and isTex1CI + and isCI and not f3dMat.tex0.use_tex_reference and not f3dMat.tex1.use_tex_reference - and f3dMat.tex0.tex_format == f3dMat.tex1.tex_format - and f3dMat.tex0.ci_format == f3dMat.tex1.ci_format ) # Without shared palette: (load pal0 -> load tex0) or (load pal1 -> load tex1) @@ -1877,14 +1895,12 @@ def saveTextureIndex( if tileSettingsOverride is not None: tileSettings = tileSettingsOverride[index] width, height = tileSettings.getDimensions() - setTLUTMode = False else: tileSettings = None if texProp.use_tex_reference: width, height = texProp.tex_reference_size else: width, height = tex.size - setTLUTMode = fModel.matWriteMethod == GfxMatWriteMethod.WriteAll nextTmem = tmem + getTmemWordUsage(texFormat, width, height) @@ -1957,8 +1973,6 @@ def saveTextureIndex( else: fImage = saveOrGetTextureDefinition(fMaterial, fModel, tex, texName, texFormat, convertTextureData) - if setTLUTMode and not isCITexture: - loadTexGfx.commands.append(DPSetTextureLUT("G_TT_NONE")) if loadTextures: saveTextureLoading( fMaterial, @@ -2166,10 +2180,6 @@ def savePaletteLoading(loadTexGfx, revertTexGfx, fPalette, palFormat, pal, color cms = ["G_TX_WRAP", "G_TX_NOMIRROR"] cmt = ["G_TX_WRAP", "G_TX_NOMIRROR"] - loadTexGfx.commands.append(DPSetTextureLUT("G_TT_RGBA16" if palFmt == "G_IM_FMT_RGBA" else "G_TT_IA16")) - if matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: - revertTexGfx.commands.append(DPSetTextureLUT("G_TT_NONE")) - if not f3d._HW_VERSION_1: loadTexGfx.commands.extend( [ @@ -2730,16 +2740,16 @@ def saveModeSetting(fMaterial, value, defaultValue, cmdClass): fMaterial.revert.commands.append(cmdClass(defaultValue)) -def saveOtherModeHDefinition(fMaterial, settings, defaults, isHWv1, matWriteMethod): +def saveOtherModeHDefinition(fMaterial, settings, tlut, defaults, isHWv1, matWriteMethod): if matWriteMethod == GfxMatWriteMethod.WriteAll: - saveOtherModeHDefinitionAll(fMaterial, settings, defaults, isHWv1) + saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, isHWv1) elif matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: - saveOtherModeHDefinitionIndividual(fMaterial, settings, defaults, isHWv1) + saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults, isHWv1) else: raise PluginError("Unhandled material write method: " + str(matWriteMethod)) -def saveOtherModeHDefinitionAll(fMaterial, settings, defaults, isHWv1): +def saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, isHWv1): cmd = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20, []) cmd.flagList.append(settings.g_mdsft_alpha_dither) if not isHWv1: @@ -2747,6 +2757,7 @@ def saveOtherModeHDefinitionAll(fMaterial, settings, defaults, isHWv1): cmd.flagList.append(settings.g_mdsft_combkey) cmd.flagList.append(settings.g_mdsft_textconv) cmd.flagList.append(settings.g_mdsft_text_filt) + cmd.flagList.append(tlut) cmd.flagList.append(settings.g_mdsft_textlod) cmd.flagList.append(settings.g_mdsft_textdetail) cmd.flagList.append(settings.g_mdsft_textpersp) @@ -2758,7 +2769,7 @@ def saveOtherModeHDefinitionAll(fMaterial, settings, defaults, isHWv1): fMaterial.material.commands.append(cmd) -def saveOtherModeHDefinitionIndividual(fMaterial, settings, defaults, isHWv1): +def saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults, isHWv1): saveModeSetting(fMaterial, settings.g_mdsft_alpha_dither, defaults.g_mdsft_alpha_dither, DPSetAlphaDither) if not isHWv1: @@ -2770,8 +2781,7 @@ def saveOtherModeHDefinitionIndividual(fMaterial, settings, defaults, isHWv1): saveModeSetting(fMaterial, settings.g_mdsft_text_filt, defaults.g_mdsft_text_filt, DPSetTextureFilter) - # saveModeSetting(fMaterial, settings.g_mdsft_textlut, - # defaults.g_mdsft_textlut, DPSetTextureLUT) + saveModeSetting(fMaterial, tlut, "G_TT_NONE", DPSetTextureLUT) saveModeSetting(fMaterial, settings.g_mdsft_textlod, defaults.g_mdsft_textlod, DPSetTextureLOD) diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 11b47007b..4ef5e73d0 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -135,6 +135,7 @@ def getTextureSuffixFromFormat(self, texFmt): return texFmt.lower() def modifyDLForCIFlipbook(self, fMaterial: FMaterial, fPalette: FMaterial, texProp: TextureProperty): + raise PluginError("TODO: modifyDLForCIFlipbook has been broken by sync and DPSetTextureLUT changes") # Modfiy DL to use new palette texture tlutCmdIndex = 0 gfxList = fMaterial.material From 1f255cca3eb19c55ef829a0f94fae4076ba7da12 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 27 Nov 2022 12:28:35 -0800 Subject: [PATCH 02/38] Added not actually reloading tex 1 if same as tex 0 --- fast64_internal/f3d/f3d_writer.py | 265 +++++++++++++++--------------- 1 file changed, 133 insertions(+), 132 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index de10766e7..70aa7d6b9 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -407,11 +407,6 @@ def saveMeshWithLargeTexturesByFaces( tmem = getTmemWordUsage(otherTex.tex_format, otherTex.tex.size[0], otherTex.tex.size[1]) * 8 if tmem <= getTmemMax(otherTex.tex_format): otherTexSingleLoad = True - # nextTmem = 0 - # revertCommands = GfxList("temp", GfxListTag.Draw, fModel.DLFormat) # Unhandled? - # texDimensions, nextTmem = \ - # saveTextureIndex(material.name, fModel, fMaterial, triGroup.triList, revertCommands, otherTex, 0, nextTmem, - # None, False, None, True, True) # saveGeometry(obj, triList, fMesh.vertexList, bFaces, # bMesh, texDimensions, transformMatrix, isPointSampled, isFlatShaded, @@ -1604,8 +1599,11 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): texDimensions0 = None texDimensions1 = None nextTmem = 0 - useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures + + if useTex0 and useTex1 and isCI: + pass # TODO + useSharedCIPalette = ( useTex0 and useTex1 @@ -1655,9 +1653,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # If the texture in both texels is the same then it can be rewritten to the same location in tmem # This allows for a texture that fills tmem to still be used for both texel0 and texel1 - if f3dMat.tex0.tex == f3dMat.tex1.tex: - if nextTmem >= (512 if f3dMat.tex0.tex_format[:2] != "CI" else 256): - nextTmem = 0 + tex1ActuallyLoad = True + if useTex0 and useTex1 and f3dMat.tex0.tex == f3dMat.tex1.tex: + nextTmem = 0 + tex1ActuallyLoad = False if useTex1: if f3dMat.tex1.tex is None and not f3dMat.tex1.use_tex_reference: @@ -1677,7 +1676,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): None, convertTextureData, None, - True, + tex1ActuallyLoad, True, sharedPalette, imageKey1, @@ -1864,8 +1863,8 @@ def saveTextureIndex( overrideName: str, convertTextureData: bool, tileSettingsOverride, - loadTextures: bool, - loadPalettes: bool, + loadTexture: bool, + loadPalette: bool, sharedPalette: FSharedPalette, imageKey: FImageKey, ) -> tuple[list[int], int, FImage]: @@ -1905,7 +1904,7 @@ def saveTextureIndex( nextTmem = tmem + getTmemWordUsage(texFormat, width, height) if not (bpy.context.scene.ignoreTextureRestrictions or fMaterial.useLargeTextures): - if nextTmem > (512 if texFormat[:2] != "CI" else 256): + if nextTmem > (512 if not isCITexture else 256): raise PluginError( 'Error in "' + propName @@ -1963,7 +1962,7 @@ def saveTextureIndex( imageKey, ) - if loadPalettes and sharedPalette is None: + if loadPalette and sharedPalette is None: savePaletteLoading( loadTexGfx, revertTexGfx, fPalette, palFormat, 0, fPalette.height, fModel.f3d, fModel.matWriteMethod ) @@ -1973,28 +1972,28 @@ def saveTextureIndex( else: fImage = saveOrGetTextureDefinition(fMaterial, fModel, tex, texName, texFormat, convertTextureData) - if loadTextures: - saveTextureLoading( - fMaterial, - fImage, - loadTexGfx, - clamp_S, - mirror_S, - clamp_T, - mirror_T, - mask_S, - mask_T, - shift_S, - shift_T, - tex_SL, - tex_TL, - tex_SH, - tex_TH, - texFormat, - index, - fModel.f3d, - tmem, - ) + saveTextureLoading( + fMaterial, + fImage, + loadTexGfx, + clamp_S, + mirror_S, + clamp_T, + mirror_T, + mask_S, + mask_T, + shift_S, + shift_T, + tex_SL, + tex_TL, + tex_SH, + tex_TH, + texFormat, + index, + fModel.f3d, + tmem, + loadTexture, + ) texDimensions = fImage.width, fImage.height # fImage = saveTextureDefinition(fModel, tex, texName, # texFormatOf[texFormat], texBitSizeOf[texFormat]) @@ -2024,6 +2023,7 @@ def saveTextureLoading( texIndex, f3d: F3D, tmem, + loadTexture: bool, ): cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] @@ -2057,106 +2057,107 @@ def saveTextureLoading( # TODO: Use width of block to load base_width = int(SH - SL) - if siz == "G_IM_SIZ_4b": - sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + if loadTexture: + if siz == "G_IM_SIZ_4b": + sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - dxt = f3d.CALC_DXT_4b(fImage.width) - line = (((base_width + 1) >> 1) + 7) >> 3 + dxt = f3d.CALC_DXT_4b(fImage.width) + line = (((base_width + 1) >> 1) + 7) >> 3 - if useLoadBlock: - loadTexGfx.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_16b", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, 0, 0, (((fImage.width) * (fImage.height) + 3) >> 2) - 1, dxt - ), - ] - ) - else: - loadTexGfx.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_8b", - line, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl2, tl, sh2, th), - ] - ) + if useLoadBlock: + loadTexGfx.commands.extend( + [ + DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), + DPSetTile( + fmt, + "G_IM_SIZ_16b", + 0, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadBlock( + f3d.G_TX_LOADTILE - texIndex, 0, 0, (((fImage.width) * (fImage.height) + 3) >> 2) - 1, dxt + ), + ] + ) + else: + loadTexGfx.commands.extend( + [ + DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), + DPSetTile( + fmt, + "G_IM_SIZ_8b", + line, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl2, tl, sh2, th), + ] + ) - else: - dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) - # Note that _LINE_BYTES and _TILE_BYTES variables are the same. - line = int((base_width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 - - if useLoadBlock: - loadTexGfx.commands.extend( - [ - # Load Block version - DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), - DPSetTile( - fmt, - siz + "_LOAD_BLOCK", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, - 0, - 0, - ( - ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) - >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] - ) - - 1, - dxt, - ), - ] - ) else: - loadTexGfx.commands.extend( - [ - # Load Tile version - DPSetTextureImage(fmt, siz, fImage.width, fImage), - DPSetTile( - fmt, siz, line, tmem, f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, cms, masks, shifts - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th), - ] - ) # added in + dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) + # Note that _LINE_BYTES and _TILE_BYTES variables are the same. + line = int((base_width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 + + if useLoadBlock: + loadTexGfx.commands.extend( + [ + # Load Block version + DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), + DPSetTile( + fmt, + siz + "_LOAD_BLOCK", + 0, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadBlock( + f3d.G_TX_LOADTILE - texIndex, + 0, + 0, + ( + ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) + >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] + ) + - 1, + dxt, + ), + ] + ) + else: + loadTexGfx.commands.extend( + [ + # Load Tile version + DPSetTextureImage(fmt, siz, fImage.width, fImage), + DPSetTile( + fmt, siz, line, tmem, f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, cms, masks, shifts + ), + DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th), + ] + ) # added in tileSizeCommand = DPSetTileSize(f3d.G_TX_RENDERTILE + texIndex, sl, tl, sh, th) loadTexGfx.commands.extend( From 2f185a5ae54b28a86c44c5bece83a50eade33e60 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 27 Nov 2022 12:52:28 -0800 Subject: [PATCH 03/38] Fixed bug in not loading tex 1 change --- fast64_internal/f3d/f3d_writer.py | 199 +++++++++++++++--------------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 70aa7d6b9..dda010adc 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -2057,107 +2057,106 @@ def saveTextureLoading( # TODO: Use width of block to load base_width = int(SH - SL) - if loadTexture: - if siz == "G_IM_SIZ_4b": - sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - - dxt = f3d.CALC_DXT_4b(fImage.width) - line = (((base_width + 1) >> 1) + 7) >> 3 - - if useLoadBlock: - loadTexGfx.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_16b", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, 0, 0, (((fImage.width) * (fImage.height) + 3) >> 2) - 1, dxt - ), - ] - ) - else: - loadTexGfx.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_8b", - line, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl2, tl, sh2, th), - ] - ) - - else: - dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) - # Note that _LINE_BYTES and _TILE_BYTES variables are the same. - line = int((base_width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 + if siz == "G_IM_SIZ_4b": + sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + + dxt = f3d.CALC_DXT_4b(fImage.width) + line = (((base_width + 1) >> 1) + 7) >> 3 + + if loadTexture and useLoadBlock: + loadTexGfx.commands.extend( + [ + DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), + DPSetTile( + fmt, + "G_IM_SIZ_16b", + 0, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadBlock( + f3d.G_TX_LOADTILE - texIndex, 0, 0, (((fImage.width) * (fImage.height) + 3) >> 2) - 1, dxt + ), + ] + ) + elif loadTexture: + loadTexGfx.commands.extend( + [ + DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), + DPSetTile( + fmt, + "G_IM_SIZ_8b", + line, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl2, tl, sh2, th), + ] + ) - if useLoadBlock: - loadTexGfx.commands.extend( - [ - # Load Block version - DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), - DPSetTile( - fmt, - siz + "_LOAD_BLOCK", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, - 0, - 0, - ( - ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) - >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] - ) - - 1, - dxt, - ), - ] - ) - else: - loadTexGfx.commands.extend( - [ - # Load Tile version - DPSetTextureImage(fmt, siz, fImage.width, fImage), - DPSetTile( - fmt, siz, line, tmem, f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, cms, masks, shifts - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th), - ] - ) # added in + else: + dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) + # Note that _LINE_BYTES and _TILE_BYTES variables are the same. + line = int((base_width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 + + if loadTexture and useLoadBlock: + loadTexGfx.commands.extend( + [ + # Load Block version + DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), + DPSetTile( + fmt, + siz + "_LOAD_BLOCK", + 0, + tmem, + f3d.G_TX_LOADTILE - texIndex, + 0, + cmt, + maskt, + shiftt, + cms, + masks, + shifts, + ), + DPLoadBlock( + f3d.G_TX_LOADTILE - texIndex, + 0, + 0, + ( + ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) + >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] + ) + - 1, + dxt, + ), + ] + ) + elif loadTexture: + loadTexGfx.commands.extend( + [ + # Load Tile version + DPSetTextureImage(fmt, siz, fImage.width, fImage), + DPSetTile( + fmt, siz, line, tmem, f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, cms, masks, shifts + ), + DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th), + ] + ) # added in tileSizeCommand = DPSetTileSize(f3d.G_TX_RENDERTILE + texIndex, sl, tl, sh, th) loadTexGfx.commands.extend( From a44b1c8ddd9c12c18f676ab7a20dbea8ee631ff0 Mon Sep 17 00:00:00 2001 From: scut Date: Mon, 28 Nov 2022 18:45:32 -0500 Subject: [PATCH 04/38] added hashing to gbi base classes --- fast64_internal/f3d/f3d_gbi.py | 154 ++++++++++++++++----------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 2dfe29144..42a9b99f1 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -3146,7 +3146,7 @@ def gsSPNoOp(f3d): # base class for gbi macros -@dataclass +@dataclass(unsafe_hash = True) class GbiMacro: _segptrs = False _ptr_amp = False @@ -3179,7 +3179,7 @@ def size(self, f3d): return GFX_SIZE -@dataclass +@dataclass(unsafe_hash = True) class SPMatrix(GbiMacro): matrix: int param: int @@ -3196,7 +3196,7 @@ def to_binary(self, f3d, segments): # Divide mesh drawing by materials into separate gfx -@dataclass +@dataclass(unsafe_hash = True) class SPVertex(GbiMacro): # v = seg pointer, n = count, v0 = ? vertList: VtxList @@ -3233,7 +3233,7 @@ def to_c(self, static=True): return header + ", " + str(self.count) + ", " + str(self.index) + ")" -@dataclass +@dataclass(unsafe_hash = True) class SPViewport(GbiMacro): # v = seg pointer, n = count, v0 = ? viewport: Vp @@ -3248,7 +3248,7 @@ def to_binary(self, f3d, segments): return gsDma1p(f3d.G_MOVEMEM, vpPtr, VP_SIZE, f3d.G_MV_VIEWPORT) -@dataclass +@dataclass(unsafe_hash = True) class SPDisplayList(GbiMacro): displayList: GfxList @@ -3269,7 +3269,7 @@ def to_c(self, static=True): return "glistp = " + self.displayList.name + "(glistp)" -@dataclass +@dataclass(unsafe_hash = True) class SPBranchList(GbiMacro): displayList: GfxList _ptr_amp = True # add an ampersand to names @@ -3374,7 +3374,7 @@ def _gsSP1Quadrangle_w2f(v0, v1, v2, v3, flag): return _gsSP1Triangle_w1(v3, v1, v2) -@dataclass +@dataclass(unsafe_hash = True) class SP1Triangle(GbiMacro): v0: int v1: int @@ -3390,7 +3390,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPLine3D(GbiMacro): v0: int v1: int @@ -3404,7 +3404,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPLineW3D(GbiMacro): v0: int v1: int @@ -3422,7 +3422,7 @@ def to_binary(self, f3d, segments): # SP1Quadrangle -@dataclass +@dataclass(unsafe_hash = True) class SP2Triangles(GbiMacro): v00: int v01: int @@ -3444,7 +3444,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPCullDisplayList(GbiMacro): vstart: int vend: int @@ -3457,7 +3457,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPSegment(GbiMacro): segment: int base: int @@ -3470,7 +3470,7 @@ def to_c(self, static=True): return header + str(self.segment) + ", " + "0x" + format(self.base, "X") + ")" -@dataclass +@dataclass(unsafe_hash = True) class SPClipRatio(GbiMacro): ratio: int @@ -3494,7 +3494,7 @@ def size(self, f3d): # SPForceMatrix -@dataclass +@dataclass(unsafe_hash = True) class SPModifyVertex(GbiMacro): vtx: int where: int @@ -3515,7 +3515,7 @@ def to_binary(self, f3d, segments): # SPBranchLessZ -@dataclass +@dataclass(unsafe_hash = True) class SPBranchLessZraw(GbiMacro): dl: GfxList vtx: int @@ -3550,7 +3550,7 @@ def size(self, f3d): # SPDmaWrite -@dataclass +@dataclass(unsafe_hash = True) class SPNumLights(GbiMacro): # n is macro name (string) n: str @@ -3559,7 +3559,7 @@ def to_binary(self, f3d, segments): return gsMoveWd(f3d.G_MW_NUMLIGHT, f3d.G_MWO_NUMLIGHT, f3d.NUML(self.n), f3d) -@dataclass +@dataclass(unsafe_hash = True) class SPLight(GbiMacro): # n is macro name (string) light: int # start address of light @@ -3575,7 +3575,7 @@ def to_binary(self, f3d, segments): return data -@dataclass +@dataclass(unsafe_hash = True) class SPLightColor(GbiMacro): # n is macro name (string) n: str @@ -3591,7 +3591,7 @@ def to_c(self, static=True): return header + str(self.n) + ", 0x" + format(self.col, "08X") + ")" -@dataclass +@dataclass(unsafe_hash = True) class SPSetLights(GbiMacro): lights: Lights @@ -3654,7 +3654,7 @@ def gsSPLookAtY(l, f3d): return gsDma1p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LOOKATY) -@dataclass +@dataclass(unsafe_hash = True) class SPLookAt(GbiMacro): la: LookAt _ptr_amp = True # add an ampersand to names @@ -3664,7 +3664,7 @@ def to_binary(self, f3d, segments): return gsSPLookAtX(light0Ptr, f3d) + gsSPLookAtY(light0Ptr + 16, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetHilite1Tile(GbiMacro): tile: int hilite: Hilite @@ -3682,7 +3682,7 @@ def to_binary(self, f3d, segments): ).to_binary(f3d, segments) -@dataclass +@dataclass(unsafe_hash = True) class DPSetHilite2Tile(GbiMacro): tile: int hilite: Hilite @@ -3700,7 +3700,7 @@ def to_binary(self, f3d, segments): ).to_binary(f3d, segments) -@dataclass +@dataclass(unsafe_hash = True) class SPFogFactor(GbiMacro): fm: int fo: int @@ -3730,7 +3730,7 @@ def to_c(self, static=True): return header + str(self.minVal) + ", " + str(self.maxVal) + ")" -@dataclass +@dataclass(unsafe_hash = True) class SPTexture(GbiMacro): s: int t: int @@ -3762,7 +3762,7 @@ def to_binary(self, f3d, segments): # SPTextureL -@dataclass +@dataclass(unsafe_hash = True) class SPPerspNormalize(GbiMacro): s: int @@ -3774,7 +3774,7 @@ def to_binary(self, f3d, segments): # SPPopMatrix -@dataclass +@dataclass(unsafe_hash = True) class SPEndDisplayList(GbiMacro): def to_binary(self, f3d, segments): words = _SHIFTL(f3d.G_ENDDL, 24, 8), 0 @@ -3826,7 +3826,7 @@ def geoFlagListToWord(flagList, f3d): return word -@dataclass +@dataclass(unsafe_hash = True) class SPGeometryMode(GbiMacro): clearFlagList: list setFlagList: list @@ -3841,7 +3841,7 @@ def to_binary(self, f3d, segments): raise PluginError("GeometryMode only available in F3DEX_GBI_2.") -@dataclass +@dataclass(unsafe_hash = True) class SPSetGeometryMode(GbiMacro): flagList: list @@ -3854,7 +3854,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPClearGeometryMode(GbiMacro): flagList: list @@ -3867,7 +3867,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPLoadGeometryMode(GbiMacro): flagList: list @@ -3887,7 +3887,7 @@ def gsSPSetOtherMode(cmd, sft, length, data, f3d): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class SPSetOtherMode(GbiMacro): cmd: str sft: int @@ -3903,7 +3903,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(cmd, sft, self.length, data, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPPipelineMode(GbiMacro): # mode is a string mode: str @@ -3916,7 +3916,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_PIPELINE, 1, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetCycleType(GbiMacro): # mode is a string mode: str @@ -3933,7 +3933,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_CYCLETYPE, 2, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTexturePersp(GbiMacro): # mode is a string mode: str @@ -3946,7 +3946,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTPERSP, 1, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureDetail(GbiMacro): # mode is a string mode: str @@ -3961,7 +3961,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTDETAIL, 2, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureLOD(GbiMacro): # mode is a string mode: str @@ -3974,7 +3974,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTLOD, 1, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureLUT(GbiMacro): # mode is a string mode: str @@ -3991,7 +3991,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTLUT, 2, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureFilter(GbiMacro): # mode is a string mode: str @@ -4006,7 +4006,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTFILT, 2, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureConvert(GbiMacro): # mode is a string mode: str @@ -4021,7 +4021,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTCONV, 3, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetCombineKey(GbiMacro): # mode is a string mode: str @@ -4034,7 +4034,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_COMBKEY, 1, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetColorDither(GbiMacro): # mode is a string mode: str @@ -4060,7 +4060,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_COLORDITHER, 1, modeVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetAlphaDither(GbiMacro): # mode is a string mode: str @@ -4080,7 +4080,7 @@ def to_binary(self, f3d, segments): raise PluginError("SetAlphaDither not available in HW v1.") -@dataclass +@dataclass(unsafe_hash = True) class DPSetAlphaCompare(GbiMacro): # mask is a string mode: str @@ -4095,7 +4095,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_ALPHACOMPARE, 2, maskVal, f3d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetDepthSource(GbiMacro): # src is a string src: str @@ -4124,7 +4124,7 @@ def GBL_c2(m1a, m1b, m2a, m2b): return (m1a) << 28 | (m1b) << 24 | (m2a) << 20 | (m2b) << 16 -@dataclass +@dataclass(unsafe_hash = True) class DPSetRenderMode(GbiMacro): # bl0-3 are string for each blender enum def __init__(self, flagList, blendList): @@ -4203,7 +4203,7 @@ def gsSetImage(cmd, fmt, siz, width, i): # DPSetDepthImage -@dataclass +@dataclass(unsafe_hash = True) class DPSetTextureImage(GbiMacro): fmt: str siz: str @@ -4246,7 +4246,7 @@ def GCCc1w1(sbRGB1, saA1, mA1, aRGB1, sbA1, aA1): ) -@dataclass +@dataclass(unsafe_hash = True) class DPSetCombineMode(GbiMacro): # all strings a0: str @@ -4299,7 +4299,7 @@ def sDPRGBColor(cmd, r, g, b, a): return gsDPSetColor(cmd, (_SHIFTL(r, 24, 8) | _SHIFTL(g, 16, 8) | _SHIFTL(b, 8, 8) | _SHIFTL(a, 0, 8))) -@dataclass +@dataclass(unsafe_hash = True) class DPSetEnvColor(GbiMacro): r: int g: int @@ -4310,7 +4310,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETENVCOLOR, self.r, self.g, self.b, self.a) -@dataclass +@dataclass(unsafe_hash = True) class DPSetBlendColor(GbiMacro): r: int g: int @@ -4321,7 +4321,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETBLENDCOLOR, self.r, self.g, self.b, self.a) -@dataclass +@dataclass(unsafe_hash = True) class DPSetFogColor(GbiMacro): r: int g: int @@ -4332,7 +4332,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETFOGCOLOR, self.r, self.g, self.b, self.a) -@dataclass +@dataclass(unsafe_hash = True) class DPSetFillColor(GbiMacro): d: int @@ -4340,7 +4340,7 @@ def to_binary(self, f3d, segments): return gsDPSetColor(f3d.G_SETFILLCOLOR, self.d) -@dataclass +@dataclass(unsafe_hash = True) class DPSetPrimDepth(GbiMacro): z: int = 0 dz: int = 0 @@ -4349,7 +4349,7 @@ def to_binary(self, f3d, segments): return gsDPSetColor(f3d.G_SETPRIMDEPTH, _SHIFTL(self.z, 16, 16) | _SHIFTL(self.dz, 0, 16)) -@dataclass +@dataclass(unsafe_hash = True) class DPSetPrimColor(GbiMacro): m: int l: int @@ -4365,7 +4365,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPSetOtherMode(GbiMacro): mode0: list mode1: list @@ -4382,7 +4382,7 @@ def gsDPLoadTileGeneric(c, tile, uls, ult, lrs, lrt): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPSetTileSize(GbiMacro): t: int uls: int @@ -4397,7 +4397,7 @@ def is_LOADTILE(self, f3d): return self.t == f3d.G_TX_LOADTILE -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTile(GbiMacro): t: int uls: int @@ -4409,7 +4409,7 @@ def to_binary(self, f3d, segments): return gsDPLoadTileGeneric(f3d.G_LOADTILE, self.t, self.uls, self.ult, self.lrs, self.lrt) -@dataclass +@dataclass(unsafe_hash = True) class DPSetTile(GbiMacro): fmt: str siz: str @@ -4450,7 +4450,7 @@ def is_LOADTILE(self, f3d): return self.tile == f3d.G_TX_LOADTILE -@dataclass +@dataclass(unsafe_hash = True) class DPLoadBlock(GbiMacro): tile: int uls: int @@ -4467,7 +4467,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTLUTCmd(GbiMacro): tile: int count: int @@ -4477,7 +4477,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTextureBlock(GbiMacro): timg: FImage fmt: str @@ -4550,7 +4550,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTextureBlockYuv(GbiMacro): timg: FImage fmt: str @@ -4628,7 +4628,7 @@ def size(self, f3d): # gsDPLoadTextureBlockYuvS -@dataclass +@dataclass(unsafe_hash = True) class _DPLoadTextureBlock(GbiMacro): timg: FImage tmem: int @@ -4707,7 +4707,7 @@ def size(self, f3d): # gsDPLoadMultiBlockS -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTextureBlock_4b(GbiMacro): timg: FImage fmt: str @@ -4778,7 +4778,7 @@ def size(self, f3d): # _gsDPLoadTextureBlock_4b -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTextureTile(GbiMacro): timg: FImage fmt: str @@ -4854,7 +4854,7 @@ def size(self, f3d): # gsDPLoadMultiTile -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTextureTile_4b(GbiMacro): timg: FImage fmt: str @@ -4930,7 +4930,7 @@ def size(self, f3d): # gsDPLoadMultiTile_4b -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTLUT_pal16(GbiMacro): pal: int dram: FImage # pallete object @@ -4972,7 +4972,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTLUT_pal256(GbiMacro): dram: FImage # pallete object _ptr_amp = True # adds & to name of image @@ -5011,7 +5011,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass +@dataclass(unsafe_hash = True) class DPLoadTLUT(GbiMacro): count: int tmemaddr: int @@ -5058,7 +5058,7 @@ def size(self, f3d): # gsDPFillRectangle -@dataclass +@dataclass(unsafe_hash = True) class DPSetConvert(GbiMacro): k0: int k1: int @@ -5074,7 +5074,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPSetKeyR(GbiMacro): cR: int sR: int @@ -5087,7 +5087,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass +@dataclass(unsafe_hash = True) class DPSetKeyGB(GbiMacro): cG: int sG: int @@ -5117,7 +5117,7 @@ def gsDPParam(cmd, param): # gsDPTextureRectangleFlip -@dataclass +@dataclass(unsafe_hash = True) class SPTextureRectangle(GbiMacro): xl: int yl: int @@ -5148,7 +5148,7 @@ def size(self, f3d): return GFX_SIZE * 2 -@dataclass +@dataclass(unsafe_hash = True) class SPScisTextureRectangle(GbiMacro): xl: int yl: int @@ -5171,25 +5171,25 @@ def size(self, f3d): # gsDPWord -@dataclass +@dataclass(unsafe_hash = True) class DPFullSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPFULLSYNC) -@dataclass +@dataclass(unsafe_hash = True) class DPTileSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPTILESYNC) -@dataclass +@dataclass(unsafe_hash = True) class DPPipeSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPPIPESYNC) -@dataclass +@dataclass(unsafe_hash = True) class DPLoadSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPLOADSYNC) From 14b097eaef75c7447c76e60bde16f43e12069acc Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 28 Nov 2022 22:26:01 -0800 Subject: [PATCH 05/38] Almost done refactoring texture writing code --- fast64_internal/f3d/f3d_material.py | 2 +- fast64_internal/f3d/f3d_writer.py | 1041 ++++++++++++++------------- 2 files changed, 555 insertions(+), 488 deletions(-) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index d2c500657..daa76dcfd 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -2159,7 +2159,7 @@ class TextureProperty(bpy.types.PropertyGroup): default="0x08000000", ) pal_reference_size: bpy.props.IntProperty( - name="Texture Reference Size", + name="Palette Reference Size", min=1, default=16, ) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index dda010adc..b744cb2cb 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -46,6 +46,8 @@ def __eq__(self, __o: object) -> bool: and self.imagesSharingPalette == __o.imagesSharingPalette ) +def getImageKey(texProp: TextureProperty, useList) -> FImageKey: + return FImageKey(texProp.tex, texProp.tex_format, texProp.ci_format, useList) class FPaletteKey: def __init__(self, palFormat: str, imagesSharingPalette: list[bpy.types.Image] = []): @@ -1428,23 +1430,6 @@ def __init__(self, name): self.palette = [] -def getImageKeys(f3dMat: F3DMaterialProperty, useSharedCIPalette: bool) -> tuple[FImageKey, FImageKey]: - imageKey0 = FImageKey( - f3dMat.tex0.tex, - f3dMat.tex0.tex_format, - f3dMat.tex0.ci_format, - [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []), - ) - imageKey1 = FImageKey( - f3dMat.tex1.tex, - f3dMat.tex1.tex_format, - f3dMat.tex1.ci_format, - ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex], - ) - - return imageKey0, imageKey1 - - def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): if material.mat_ver > 3: f3dMat = material.f3d_mat @@ -1544,14 +1529,47 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): useDict = all_combiner_uses(f3dMat) - # Get tlut info for othermode - useTex0 = useDict["Texture 0"] and f3dMat.tex0.tex_set - useTex1 = useDict["Texture 1"] and f3dMat.tex1.tex_set - isTex0CI = f3dMat.tex0.tex_format[:2] == "CI" - isTex1CI = f3dMat.tex1.tex_format[:2] == "CI" - tex0CIFmt = f3dMat.tex0.ci_format - tex1CIFmt = f3dMat.tex1.ci_format - + # Get texture info, needed for othermode + ( + useTex0, + isTex0Ref, + isTex0CI, + tex0Fmt, + pal0Fmt, + tex0Name, + imageDims0, + tileDims0, + tex0Tmem, + pal0, + pal0Len, + ) = getAndCheckTexInfo( + material.name, + fModel, + f3dMat.tex0, + None, + None, + useDict["Texture 0"] + ) + ( + useTex1, + isTex1Ref, + isTex1CI, + tex1Fmt, + pal1Fmt, + imageDims1, + tileDims1, + tex1Tmem, + pal1, + pal1Len, + ) = getAndCheckTexInfo( + material.name, + fModel, + f3dMat.tex1, + None, + None, + useDict["Texture 1"] + ) + if useTex0 and useTex1: if isTex0CI != isTex1CI: raise PluginError( @@ -1560,17 +1578,41 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + ": N64 does not support CI + non-CI texture. " + "Must be both CI or neither CI." ) - if tex0CIFmt != tex1CIFmt: + if ( + isTex0Ref + and isTex1Ref + and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference + and f3dMat.tex0.tex_reference_size != f3dMat.tex1.tex_reference_size + ): raise PluginError( "In material " + material.name - + ": Both CI textures must use the same CI format." + + ": Two textures with the same reference must have the same size." ) + if isCI: + if pal0Fmt != pal1Fmt: + raise PluginError( + "In material " + + material.name + + ": Both CI textures must use the same palette format (usually RGBA16)." + ) + if ( + isTex0Ref + and isTex1Ref + and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference + and f3dMat.tex0.pal_reference_size != f3dMat.tex1.pal_reference_size + ): + raise PluginError( + "In material " + + material.name + + ": Two textures with the same palette reference must have the same palette size." + ) isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) - ci_format = tex0CIFmt if useTex0 else tex1CIFmt - tlut = "G_TT_NONE" if not isCI else ("G_TT_" + ci_format) + palFormat = pal0Fmt if useTex0 else pal1Fmt + g_tt = "G_TT_NONE" if not isCI else ("G_TT_" + palFormat) + # Set othermode if drawLayer is not None: defaultRM = fModel.getRenderMode(drawLayer) else: @@ -1581,7 +1623,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): saveGeoModeDefinitionF3DEX2(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) else: saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) - saveOtherModeHDefinition(fMaterial, f3dMat.rdp_settings, tlut, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod) + saveOtherModeHDefinition(fMaterial, f3dMat.rdp_settings, g_tt, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1595,139 +1637,205 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: fMaterial.material.commands.append(SPTexture(s, t, 0, fModel.f3d.G_TX_RENDERTILE, 1)) - # Save textures - texDimensions0 = None - texDimensions1 = None - nextTmem = 0 - useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures - - if useTex0 and useTex1 and isCI: - pass # TODO - - useSharedCIPalette = ( - useTex0 - and useTex1 - and isCI - and not f3dMat.tex0.use_tex_reference - and not f3dMat.tex1.use_tex_reference - ) - - # Without shared palette: (load pal0 -> load tex0) or (load pal1 -> load tex1) - # with shared palette: load pal -> load tex0 -> load tex1 - if useSharedCIPalette: - sharedPalette = FSharedPalette(getSharedPaletteName(f3dMat)) - - # dummy lists to be appended in later - loadGfx = GfxList(None, None, fModel.DLFormat) - revertGfx = GfxList(None, None, fModel.DLFormat) - else: - sharedPalette = None - loadGfx = fMaterial.material - revertGfx = fMaterial.revert - - imageKey0, imageKey1 = getImageKeys(f3dMat, useSharedCIPalette) - - if useTex0: - if f3dMat.tex0.tex is None and not f3dMat.tex0.use_tex_reference: - raise PluginError('In material "' + material.name + '", a texture has not been set.') - - fMaterial.useLargeTextures = useLargeTextures - fMaterial.texturesLoaded[0] = True - texDimensions0, nextTmem, fImage0 = saveTextureIndex( - material.name, - fModel, - fMaterial, - loadGfx, - revertGfx, - f3dMat.tex0, - 0, - nextTmem, - None, - convertTextureData, - None, - True, - True, - sharedPalette, - imageKey0, - ) - - # If the texture in both texels is the same then it can be rewritten to the same location in tmem - # This allows for a texture that fills tmem to still be used for both texel0 and texel1 + # Handle combinations of CI textures + TODO(convertTextureData) + tex0PaletteIndex = 0 + tex1PaletteIndex = 0 + loadPal0 = False + loadPal1 = False + pal0Addr = 0 + pal1Addr = 0 + if isCI: + assert useTex0 or useTex1 + if not useTex1: + loadPal0 = True + elif not useTex0: + loadPal1 = True + else: # Two CI textures + if tex0Fmt == "CI8" and tex1Fmt == "CI8": + if isTex0Ref != isTex0Ref: + raise PluginError( + "In material " + + material.name + + ": can't have two CI8 textures where only one is a reference; no way to assign the palette." + ) + loadPal0 = True + if isTex0Ref: + if f3dMat.tex0.pal_reference != f3dMat.tex1.pal_reference: + raise PluginError( + "In material " + + material.name + + ": can't have two CI8 textures with different palette references." + ) + else: + pal0 = mergePalettes(pal0, pal1) + pal0Len = len(pal0) + if pal0Len > 256: + raise PluginError( + "In material " + + material.name + + ": the two CI textures together contain a total of " + + str(pal0Len) + + " colors, which can't fit in a CI8 palette (256)." + ) + elif tex0Fmt != tex1Fmt: # One CI8, one CI4 + ci8Pal, ci4Pal = pal0, pal1 if tex0Fmt == "CI8" else pal1, pal0 + ci8PalLen, ci4PalLen = pal0Len, pal1Len if tex0Fmt == "CI8" else pal1Len, pal0Len + if isTex0Ref or isTex1Ref: + if ci8PalLen > 256 - 16: + raise PluginError( + "In material " + + material.name + + ": the CI8 texture has over 240 colors, which can't fit together with the CI4 palette." + ) + loadPal0 = loadPal1 = True + if tex0Fmt == "CI8": + tex1PaletteIndex = 15 + pal1Addr = 240 + else: + tex0PaletteIndex = 15 + pal0Addr = 240 + else: + # CI4 indices in palette 0, CI8 indices start from palette 0 + loadPal0 = True + pal0 = mergePalettes(ci4Pal, ci8Pal) + pal0Len = len(pal0) + if pal0Len > 256: + raise PluginError( + "In material " + + material.name + + ": the two CI textures together contain a total of " + + str(pal0Len) + + " colors, which can't fit in a CI8 palette (256)." + + " The CI8 texture must contain up to 240 unique colors," + + " plus the same up to 16 colors used in the CI4 texture." + ) + else: # both CI4 textures + if isTex0Ref and isTex1Ref and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: + loadPal0 = True + elif isTex0Ref or isTex1Ref: + loadPal0 = loadPal1 = True + tex1PaletteIndex = 1 + pal1Addr = 16 + else: + loadPal0 = True + tempPal = mergePalettes(pal0, pal1) + tempPalLen = len(tempPal) + assert tempPalLen <= 32 + if tempPalLen <= 16: + # Share palette 0 + pal0 = tempPal + pal0Len = tempPalLen + else: + # Load one palette across 0-1. Put the longer in slot 0 + if pal0Len >= pal1Len: + while len(pal0) < 16: + pal0.append(0) + pal0.extend(pal1) + pal0Len = len(pal0) + tex1PaletteIndex = 1 + else: + while len(pal1) < 16: + pal1.append(0) + pal0 = pal1 + pal0 + pal0Len = len(pal0) + tex0PaletteIndex = 1 + useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 + + # Handle same texture in both slots (only load once) tex1ActuallyLoad = True - if useTex0 and useTex1 and f3dMat.tex0.tex == f3dMat.tex1.tex: - nextTmem = 0 + tex1Addr = tex0Tmem + tmemOccupied = tex0Tmem + tex1Tmem + if ( + useTex0 and useTex1 and ( + ( + not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex + ) or ( + isTex0Ref + and isTex1Ref + and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference + ) + ) + ): + assert tex0Tmem == tex1Tmem tex1ActuallyLoad = False + tmemOccupied = tex0Tmem + + useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures + fMaterial.useLargeTextures = useLargeTextures and (useTex0 or useTex1) + fMaterial.texturesLoaded[0] = useTex0 + fMaterial.texturesLoaded[1] = useTex1 + if not (bpy.context.scene.ignoreTextureRestrictions or useLargeTextures): + if tex0Tmem + tex1Tmem > (512 if not isCI else 256): + raise PluginError( + 'Error in "' + + material.name + + '": Textures are too big. Max TMEM size is 4k ' + + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." + ) + # Get texture and palette definitions + imUse0 = [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []) + imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] + fImage0 = fImage1 = fPalette0 = fPalette1 = None + if useTex0: + imageKey0 = getImageKey(f3dMat.tex0, imUse0) + fImage0, fPalette0 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex0.tex, tex0Name, tex0Fmt, imageKey0) + if loadPal0: + if fPalette0 is not None: + paletteKey0 = fImage0.paletteKey + else: + fPalette0, paletteKey0 = saveOrGetPaletteDefinition( + fMaterial, fModel, imUse0, tex0Name, tex0Fmt, pal0Fmt) + fImage0.paletteKey = paletteKey0 if useTex1: - if f3dMat.tex1.tex is None and not f3dMat.tex1.use_tex_reference: - raise PluginError('In material "' + material.name + '", a texture has not been set.') - - fMaterial.useLargeTextures = useLargeTextures - fMaterial.texturesLoaded[1] = True - texDimensions1, nextTmem, fImage1 = saveTextureIndex( - material.name, - fModel, - fMaterial, - loadGfx, - revertGfx, - f3dMat.tex1, - 1, - nextTmem, - None, - convertTextureData, - None, - tex1ActuallyLoad, - True, - sharedPalette, - imageKey1, - ) - - if useSharedCIPalette: - texFormat = f3dMat.tex0.tex_format - palFormat = f3dMat.tex0.ci_format - - fPalette, paletteKey = saveOrGetPaletteOnlyDefinition( - fMaterial, - fModel, - [f3dMat.tex0.tex, f3dMat.tex1.tex], - sharedPalette.name, - texFormat, - palFormat, - convertTextureData, - sharedPalette.palette, - ) - savePaletteLoading( - fMaterial.material, - fMaterial.revert, - fPalette, - palFormat, - 0, - fPalette.height, - fModel.f3d, - fModel.matWriteMethod, - ) - - # Append these commands after palette loading commands - fMaterial.material.commands.extend(loadGfx.commands) - fMaterial.revert.commands.extend(revertGfx.commands) - - fImage0.paletteKey = paletteKey - fImage1.paletteKey = paletteKey - + imageKey1 = getImageKey(f3dMat.tex1, imUse1) + fImage1, fPalette1 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex1.tex, tex1Name, tex1Fmt, imageKey1) + if useSharedCIPalette: + fImage1.paletteKey = paletteKey0 + if loadPal1: + if fPalette1 is not None: + paletteKey1 = fImage1.paletteKey + else: + fPalette1, paletteKey1 = saveOrGetPaletteDefinition( + fMaterial, fModel, imUse1, tex1Name, tex1Fmt, pal1Fmt) + fImage1.paletteKey = paletteKey1 + + # Write texture and palette data. This will be unset for exporting as PNGs. + if convertTextureData: + TODO() + + # Write DL entries to load textures and palettes + loadGfx = fMaterial.material + if loadPal0: + savePaletteLoad(loadGfx, fPalette0, pal0Fmt, pal0Addr, pal0Len, 5, fModel.f3d) + if useTex0: + saveTextureLoadOnly(fImage0, loadGfx, f3dMat.tex0, None, 7, 0, fModel.f3d) + saveTextureTile( + fImage0, fMaterial, loadGfx, f3dMat.tex0, None, 0, 0, tex0PaletteIndex, fModel.f3d) + if loadPal1: + savePaletteLoad(loadGfx, fPalette1, pal1Fmt, pal1Addr, pal1Len, 4, fModel.f3d) + if useTex1: + if tex1ActuallyLoad: + saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) + saveTextureTile( + fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) + # Used so we know how to convert normalized UVs when saving verts. - if texDimensions0 is not None and texDimensions1 is not None: + if imageDims0 is not None and imageDims1 is not None: if f3dMat.uv_basis == "TEXEL0": - texDimensions = texDimensions0 + texDimensions = imageDims0 fMaterial.largeTextureIndex = 0 else: - texDimensions = texDimensions1 + texDimensions = imageDims1 fMaterial.largeTextureIndex = 1 - elif texDimensions0 is not None: - texDimensions = texDimensions0 + texDimensions = imageDims0 fMaterial.largeTextureIndex = 0 elif texDimensions1 is not None: - texDimensions = texDimensions1 + texDimensions = imageDims1 fMaterial.largeTextureIndex = 1 else: texDimensions = [32, 32] @@ -1851,31 +1959,73 @@ def getTextureNameTexRef(texProp: TextureProperty, fModelName: str) -> str: return texName -def saveTextureIndex( +def extractConvertCIPixel(image, pixels, i, j, palFormat): + color = [1, 1, 1, 1] + for field in range(image.channels): + color[field] = pixels[(j * image.size[0] + i) * image.channels + field] + if palFormat == "RGBA16": + pixelColor = getRGBA16Tuple(color) + elif palFormat == "IA16": + pixelColor = getIA16Tuple(color) + else: + raise PluginError("Internal error with palette format") + return pixelColor + + +def getColorsUsedInImage(image, palFormat): + palette = [] + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + if pixelColor not in palette: + palette.append(pixelColor) + return palette + + +def mergePalettes(pal0, pal1): + palette = [c for c in pal0] + for c in pal1: + if c not in palette: + palette.append(c) + return palette + + +def getColorIndicesOfTexture(image, palette, palFormat): + texture = [] + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + texture.append(palette.index(pixelColor)) + return texture + + +def getAndCheckTexInfo( propName: str, fModel: FModel, - fMaterial: FMaterial, - loadTexGfx: GfxList, - revertTexGfx: GfxList, texProp: TextureProperty, - index: int, - tmem: int, overrideName: str, - convertTextureData: bool, - tileSettingsOverride, - loadTexture: bool, - loadPalette: bool, - sharedPalette: FSharedPalette, - imageKey: FImageKey, -) -> tuple[list[int], int, FImage]: + tileSettings, + useDictEntry, +): + if not useDictEntry or not texProp.tex_set: + return False, False, False, "", "", (0, 0), (0, 0), 0, None, 0 + tex = texProp.tex + isTexRef = texProp.use_tex_reference + texFormat = texProp.tex_format + isCITexture = texFormat[:2] == "CI" + palFormat = texProp.ci_format if isCITexture else "" if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0): raise PluginError( "Image " + tex.name + " has either a 0 width or height; image may have been removed from original location." ) - if not texProp.use_tex_reference: + if not isTexRef: if tex is None: raise PluginError("In " + propName + ", no texture is selected.") elif len(tex.pixels) == 0: @@ -1885,284 +2035,189 @@ def saveTextureIndex( + ". Make sure this texture has not been deleted or moved on disk." ) - texFormat = texProp.tex_format - isCITexture = texFormat[:2] == "CI" - palFormat = texProp.ci_format if isCITexture else "" - - texName = getTextureName(texProp, fModel.name, overrideName) - - if tileSettingsOverride is not None: - tileSettings = tileSettingsOverride[index] - width, height = tileSettings.getDimensions() + if isTexRef: + imageWidth, imageHeight = texProp.tex_reference_size else: - tileSettings = None - if texProp.use_tex_reference: - width, height = texProp.tex_reference_size - else: - width, height = tex.size + imageWidth, imageHeight = tex.size + + if tileSettings is not None: + tileWidth, tileHeight = tileSettings.getDimensions() + else: + tileWidth, tileHeight = imageWidth, imageHeight - nextTmem = tmem + getTmemWordUsage(texFormat, width, height) + tmemSize = getTmemWordUsage(texFormat, tileWidth, tileHeight) - if not (bpy.context.scene.ignoreTextureRestrictions or fMaterial.useLargeTextures): - if nextTmem > (512 if not isCITexture else 256): - raise PluginError( - 'Error in "' - + propName - + '": Textures are too big. Max TMEM size is 4k ' - + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." - ) - if width > 1024 or height > 1024: + if tileWidth > 1024 or tileHeight > 1024: raise PluginError('Error in "' + propName + '": Any side of an image cannot be greater ' + "than 1024.") - if tileSettings is None: - clamp_S = texProp.S.clamp - mirror_S = texProp.S.mirror - tex_SL = texProp.S.low - tex_SH = texProp.S.high - mask_S = texProp.S.mask - shift_S = texProp.S.shift - - clamp_T = texProp.T.clamp - mirror_T = texProp.T.mirror - tex_TL = texProp.T.low - tex_TH = texProp.T.high - mask_T = texProp.T.mask - shift_T = texProp.T.shift - - else: - clamp_S = True - mirror_S = False - tex_SL = tileSettings.sl - tex_SH = tileSettings.sh - mask_S = 0 - shift_S = 0 - - clamp_T = True - mirror_T = False - tex_TL = tileSettings.tl - tex_TH = tileSettings.th - mask_T = 0 - shift_T = 0 - + pal = None + palLen = 0 if isCITexture: - if texProp.use_tex_reference: - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - fPalette = FImage(texProp.pal_reference, None, None, 1, texProp.pal_reference_size, None, False) + if isTexRef: + palLen = texProp.pal_reference_size else: - # fPalette should be an fImage here, since sharedPalette is None - fImage, fPalette, alreadyExists = saveOrGetPaletteAndImageDefinition( - fMaterial, - fModel, - tex, - texName, - texFormat, - palFormat, - convertTextureData, - sharedPalette, - imageKey, - ) - - if loadPalette and sharedPalette is None: - savePaletteLoading( - loadTexGfx, revertTexGfx, fPalette, palFormat, 0, fPalette.height, fModel.f3d, fModel.matWriteMethod + pal = getColorsUsedInImage(tex, palFormat) + palLen = len(pal0) + if palLen > (16 if texFormat == "CI4" else 256): + raise PluginError( + "In " + + propName + + ", texture " + + tex.name + + " uses too many unique colors to fit in format" + + texFormat + + "." ) - else: - if texProp.use_tex_reference: - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - else: - fImage = saveOrGetTextureDefinition(fMaterial, fModel, tex, texName, texFormat, convertTextureData) - - saveTextureLoading( - fMaterial, - fImage, - loadTexGfx, - clamp_S, - mirror_S, - clamp_T, - mirror_T, - mask_S, - mask_T, - shift_S, - shift_T, - tex_SL, - tex_TL, - tex_SH, - tex_TH, + + return ( + True, + isTexRef, + isCITexture, texFormat, - index, - fModel.f3d, - tmem, - loadTexture, + palFormat, + (imageWidth, imageHeight), + (tileWidth, tileHeight), + tmemSize, + pal, + palLen ) - texDimensions = fImage.width, fImage.height - # fImage = saveTextureDefinition(fModel, tex, texName, - # texFormatOf[texFormat], texBitSizeOf[texFormat]) - # fModel.textures[texName] = fImage - - return texDimensions, nextTmem, fImage - - -# texIndex: 0 for texture0, 1 for texture1 -def saveTextureLoading( - fMaterial, - fImage, - loadTexGfx, - clamp_S, - mirror_S, - clamp_T, - mirror_T, - mask_S, - mask_T, - shift_S, - shift_T, - SL, - TL, - SH, - TH, - tex_format, - texIndex, - f3d: F3D, - tmem, - loadTexture: bool, -): - cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] - cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] - masks = mask_S - maskt = mask_T - shifts = shift_S if shift_S >= 0 else (shift_S + 16) - shiftt = shift_T if shift_T >= 0 else (shift_T + 16) - - # print('Low ' + str(SL) + ' ' + str(TL)) + + +def getTileSizeSettings(texProp: TextureProperty, tileSettings, f3d: F3D): + if tileSettings is not None: + SL = tileSettings.sl + TL = tileSettings.tl + SH = tileSettings.sh + TH = tileSettings.th + else: + SL = texProp.S.low + TL = texProp.T.low + SH = texProp.S.high + TH = texProp.T.high sl = int(SL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) tl = int(TL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) sh = int(SH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) th = int(TH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) + return SL, TL, SH, TH, sl, tl, sh, th + + +def getTileLine(fImage: FImage, SL: int, SH: int, siz: str, f3d: F3D): + width = int(SH - SL) if fImage.isLargeTexture else int(fImage.width) + if siz == "G_IM_SIZ_4b": + line = (((width + 1) >> 1) + 7) >> 3 + else: + # Note that _LINE_BYTES and _TILE_BYTES variables are the same. + line = int((width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 + return line - fmt = texFormatOf[tex_format] - siz = texBitSizeOf[tex_format] - pal = 0 if fmt[:2] != "CI" else 0 # handle palettes - # texelsPerWord = int(round(64 / bitSizeDict[siz])) - useLoadBlock = not fImage.isLargeTexture and isPowerOf2(fImage.width) and isPowerOf2(fImage.height) +def saveTextureLoadOnly( + fImage: FImage, + gfxOut: GfxList, + texProp: TextureProperty, + tileSettings, + loadtile: int, + tmem: int, + f3d: F3D, +): + fmt = texFormatOf[texProp.tex_format] + siz = texBitSizeOf[texProp.tex_format] + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) + line = getTileLine(fImage, SL, SH, siz, f3d) # LoadTile will pad rows to 64 bit word alignment, while # LoadBlock assumes this is already done. - - # These commands are basically DPLoadMultiBlock/Tile, - # except for the load tile index which will be 6 instead of 7 for render tile = 1. - # This may be unnecessary, but at this point DPLoadMultiBlock/Tile is not implemented yet - # so it would be extra work for the same outcome. - base_width = int(fImage.width) - if fImage.isLargeTexture: - # TODO: Use width of block to load - base_width = int(SH - SL) + useLoadBlock = not fImage.isLargeTexture and isPowerOf2(fImage.width) if siz == "G_IM_SIZ_4b": - sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - - dxt = f3d.CALC_DXT_4b(fImage.width) - line = (((base_width + 1) >> 1) + 7) >> 3 - - if loadTexture and useLoadBlock: - loadTexGfx.commands.extend( + if useLoadBlock: + dxs = (((fImage.width) * (fImage.height) + 3) >> 2) - 1 + dxt = f3d.CALC_DXT_4b(fImage.width) + gfxOut.commands.extend( [ DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_16b", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, 0, 0, (((fImage.width) * (fImage.height) + 3) >> 2) - 1, dxt - ), + DPSetTile(fmt, "G_IM_SIZ_16b", 0, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadBlock(loadtile, 0, 0, dxs, dxt), ] ) - elif loadTexture: - loadTexGfx.commands.extend( + else: + sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + gfxOut.commands.extend( [ DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), - DPSetTile( - fmt, - "G_IM_SIZ_8b", - line, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl2, tl, sh2, th), + DPSetTile(fmt, "G_IM_SIZ_8b", line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadTile(loadtile, sl2, tl, sh2, th), ] ) - else: - dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) - # Note that _LINE_BYTES and _TILE_BYTES variables are the same. - line = int((base_width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 - - if loadTexture and useLoadBlock: - loadTexGfx.commands.extend( + if useLoadBlock: + dxs = (((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) + >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"]) - 1 + dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) + gfxOut.commands.extend( [ - # Load Block version DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), - DPSetTile( - fmt, - siz + "_LOAD_BLOCK", - 0, - tmem, - f3d.G_TX_LOADTILE - texIndex, - 0, - cmt, - maskt, - shiftt, - cms, - masks, - shifts, - ), - DPLoadBlock( - f3d.G_TX_LOADTILE - texIndex, - 0, - 0, - ( - ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) - >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] - ) - - 1, - dxt, - ), + DPSetTile(fmt, siz + "_LOAD_BLOCK", 0, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadBlock(loadtile, 0, 0, dxs, dxt), ] ) - elif loadTexture: - loadTexGfx.commands.extend( + else: + gfxOut.commands.extend( [ - # Load Tile version DPSetTextureImage(fmt, siz, fImage.width, fImage), - DPSetTile( - fmt, siz, line, tmem, f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, cms, masks, shifts - ), - DPLoadTile(f3d.G_TX_LOADTILE - texIndex, sl, tl, sh, th), + DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadTile(loadtile, sl, tl, sh, th), ] - ) # added in + ) + - tileSizeCommand = DPSetTileSize(f3d.G_TX_RENDERTILE + texIndex, sl, tl, sh, th) - loadTexGfx.commands.extend( +def saveTextureTile( + fImage: FImage, + fMaterial: FMaterial, + gfxOut: GfxList, + texProp: TextureProperty, + tileSettings, + rendertile: int, + tmem: int, + pal: int, + f3d: F3D, +): + if tileSettings is not None: + clamp_S = True + clamp_T = True + mirror_S = False + mirror_T = False + mask_S = 0 + mask_T = 0 + shift_S = 0 + shift_T = 0 + else: + clamp_S = texProp.S.clamp + clamp_T = texProp.T.clamp + mirror_S = texProp.S.mirror + mirror_T = texProp.T.mirror + mask_S = texProp.S.mask + mask_T = texProp.T.mask + shift_S = texProp.S.shift + shift_T = texProp.T.shift + cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] + cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] + masks = mask_S + maskt = mask_T + shifts = shift_S if shift_S >= 0 else (shift_S + 16) + shiftt = shift_T if shift_T >= 0 else (shift_T + 16) + fmt = texFormatOf[texProp.tex_format] + siz = texBitSizeOf[texProp.tex_format] + SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) + line = getTileLine(fImage, SL, SH, siz, f3d) + + tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) + gfxOut.commands.extend( [ DPSetTile( - fmt, siz, line, tmem, f3d.G_TX_RENDERTILE + texIndex, pal, cmt, maskt, shiftt, cms, masks, shifts + fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts ), tileSizeCommand, ] @@ -2170,23 +2225,30 @@ def saveTextureLoading( # hasattr check for FTexRect if hasattr(fMaterial, "tileSizeCommands"): - fMaterial.tileSizeCommands[f3d.G_TX_RENDERTILE + texIndex] = tileSizeCommand - - -# palette stored in upper half of TMEM (words 256-511) -# pal is palette number (0-16), for CI8 always set to 0 -def savePaletteLoading(loadTexGfx, revertTexGfx, fPalette, palFormat, pal, colorCount, f3d, matWriteMethod): + fMaterial.tileSizeCommands[rendertile] = tileSizeCommand + + +# palAddr is the address within the second half of tmem (0-255), normally 16*palette num +# palLen is the number of colors +def savePaletteLoad( + gfxOut: GfxList, + fPalette: FPalette, + palFormat: str, + palAddr: int, + palLen: int, + loadtile: int, + f3d: F3D, +): + assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 palFmt = texFormatOf[palFormat] - cms = ["G_TX_WRAP", "G_TX_NOMIRROR"] - cmt = ["G_TX_WRAP", "G_TX_NOMIRROR"] + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] if not f3d._HW_VERSION_1: loadTexGfx.commands.extend( [ DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), - DPSetTile("0", "0", 0, (256 + (((pal) & 0xF) * 16)), f3d.G_TX_LOADTILE, 0, cmt, 0, 0, cms, 0, 0), - DPLoadTLUTCmd(f3d.G_TX_LOADTILE, colorCount - 1), - DPLoadSync(), + DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadTLUTCmd(loadtile, palLen - 1), ] ) else: @@ -2194,14 +2256,14 @@ def savePaletteLoading(loadTexGfx, revertTexGfx, fPalette, palFormat, pal, color [ _DPLoadTextureBlock( fPalette, - (256 + (((pal) & 0xF) * 16)), + 256 + palAddr, palFmt, "G_IM_SIZ_16b", - 4 * colorCount, + 4 * palLen, 1, - pal, - cms, - cmt, + 0, + nocm, + nocm, 0, 0, 0, @@ -2211,15 +2273,25 @@ def savePaletteLoading(loadTexGfx, revertTexGfx, fPalette, palFormat, pal, color ) -def saveOrGetPaletteOnlyDefinition( +def todo(): + if isCITexture: + if texProp.use_tex_reference: + fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) + fPalette = FImage(texProp.pal_reference, None, None, 1, texProp.pal_reference_size, None, False) + + else: + if texProp.use_tex_reference: + fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) + + + +def saveOrGetPaletteDefinition( fMaterial: FMaterial, fModel: FModel, images: list[bpy.types.Image], imageName: str, texFmt: str, palFmt: str, - convertTextureData: bool, - palette: list[int], ) -> tuple[FImage, tuple[bpy.types.Image, tuple[str, str]]]: palFormat = texFormatOf[palFmt] @@ -2234,20 +2306,60 @@ def saveOrGetPaletteOnlyDefinition( 1, len(palette), paletteFilename, - convertTextureData, + False, + ) + + fModel.addTexture(paletteKey, fPalette, fMaterial) + return fPalette, paletteKey + + +def saveOrGetTextureDefinition( + fMaterial: FMaterial, + fModel: FModel, + image: bpy.types.Image, + imageName: str, + texFmt: str, + imageKey: FImageKey, +) -> tuple[FImage, FImage, bool]: + + texFormat = texFormatOf[texFmt] + bitSize = texBitSizeOf[texFmt] + + # If image already loaded, return that data. + fImage, fPalette = fModel.getTextureAndHandleShared(imageKey) + if fImage is not None: + # print(f"Image already exists") + return fImage, fPalette + + name = image.name if image.filepath == "" else image.filepath + filename = getNameFromPath(name, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".inc.c" + + fImage = FImage( + checkDuplicateTextureName(fModel, toAlnum(imageName)), + texFormat, + bitSize, + image.size[0], + image.size[1], + filename, + False, ) if fMaterial.useLargeTextures: - fPalette.isLargeTexture = True + fImage.isLargeTexture = True - if convertTextureData: - for color in palette: - fPalette.data.extend(color.to_bytes(2, "big")) + fModel.addTexture(imageKey, fImage, fMaterial) - # print(f"Palette data: {paletteName} - length {len(fPalette.data)}") + return fImage, None + + +def writePaletteData( + TODO, + palette: list[int], +): + for color in palette: + fPalette.data.extend(color.to_bytes(2, "big")) + fPalette.converted = True - fModel.addTexture(paletteKey, fPalette, fMaterial) - return fPalette, paletteKey def saveOrGetPaletteAndImageDefinition( @@ -2278,32 +2390,17 @@ def saveOrGetPaletteAndImageDefinition( texture = [] maxColors = 16 if bitSize == "G_IM_SIZ_4b" else 256 if convertTextureData: - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - color = [1, 1, 1, 1] - for field in range(image.channels): - color[field] = pixels[(j * image.size[0] + i) * image.channels + field] - if palFormat == "G_IM_FMT_RGBA": - pixelColor = getRGBA16Tuple(color) - elif palFormat == "G_IM_FMT_IA": - pixelColor = getIA16Tuple(color) - else: - raise PluginError("Invalid combo: " + palFormat + ", " + bitSize) - - if pixelColor not in palette: - palette.append(pixelColor) - if len(palette) > maxColors: - raise PluginError( - "Texture " - + imageName - + " has more than " - + str(maxColors) - + " colors, or is part of a shared palette with too many colors." - ) - texture.append(palette.index(pixelColor)) - + palette.extend(getColorsUsedInImage(image, palFmt)) + if len(palette) > maxColors: + raise PluginError( + "Texture " + + imageName + + " has more than " + + str(maxColors) + + " colors, or is part of a shared palette with too many colors." + ) + texture = getColorIndicesOfTexture(image, palette, palFmt) + if image.filepath == "": name = image.name else: @@ -2375,36 +2472,10 @@ def checkDuplicateTextureName(fModelOrTexRect, name): return name -def saveOrGetTextureDefinition(fMaterial, fModel, image: bpy.types.Image, imageName, texFormat, convertTextureData): +def TODO_saveTextureData(fMaterial, fModel, image: bpy.types.Image, imageName, texFormat, convertTextureData): fmt = texFormatOf[texFormat] bitSize = texBitSizeOf[texFormat] - # If image already loaded, return that data. - # We use NONE here for pal format since this function is only to be called for non-ci textures. - imageKey = FImageKey(image, texFormat, "NONE") - fImage, fPalette = fModel.getTextureAndHandleShared(imageKey) - if fImage is not None: - return fImage - - if image.filepath == "": - name = image.name - else: - name = image.filepath - filename = getNameFromPath(name, True) + "." + fModel.getTextureSuffixFromFormat(texFormat) + ".inc.c" - - fImage = FImage( - checkDuplicateTextureName(fModel, toAlnum(imageName)), - fmt, - bitSize, - image.size[0], - image.size[1], - filename, - convertTextureData, - ) - if fMaterial.useLargeTextures: - fImage.isLargeTexture = True - - if convertTextureData: pixels = image.pixels[:] # print(f"Converting texture data for {filename}") if fmt == "G_IM_FMT_RGBA": @@ -2609,10 +2680,6 @@ def saveOrGetTextureDefinition(fMaterial, fModel, image: bpy.types.Image, imageN if bitSize == "G_IM_SIZ_4b": fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) - print("Finished converting.") - fModel.addTexture(imageKey, fImage, fMaterial) - - return fImage def saveLightsDefinition(fModel, fMaterial, material, lightsName): From 2863fdc93bb28c446931b46a0753e6a2c626daa8 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Fri, 2 Dec 2022 21:14:21 -0800 Subject: [PATCH 06/38] Initial incomplete draft of texture rewrite --- fast64_internal/f3d/f3d_writer.py | 767 ++++++++++++++---------------- 1 file changed, 359 insertions(+), 408 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index b744cb2cb..4c6ee7c0c 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1529,7 +1529,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): useDict = all_combiner_uses(f3dMat) - # Get texture info, needed for othermode + # Get texture info, needed for othermode; also check some texture props ( useTex0, isTex0Ref, @@ -1637,8 +1637,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: fMaterial.material.commands.append(SPTexture(s, t, 0, fModel.f3d.G_TX_RENDERTILE, 1)) - # Handle combinations of CI textures - TODO(convertTextureData) + # Determine how to arrange / load palette entries into upper half of tmem tex0PaletteIndex = 0 tex1PaletteIndex = 0 loadPal0 = False @@ -1651,7 +1650,18 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): loadPal0 = True elif not useTex0: loadPal1 = True - else: # Two CI textures + elif not convertTextureData: + if tex0Fmt == "CI8" or tex1Fmt == "CI8": + raise PluginError( + "In material " + + material.name + + ": When using export as PNGs mode, can't have multitexture with one or more CI8 textures." + + " Only single CI texture or two CI4 textures." + ) + loadPal0 = loadPal1 = True + tex1PaletteIndex = 1 + pal1Addr = 16 + else: # Two CI textures, normal mode if tex0Fmt == "CI8" and tex1Fmt == "CI8": if isTex0Ref != isTex0Ref: raise PluginError( @@ -1779,34 +1789,28 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: - imageKey0 = getImageKey(f3dMat.tex0, imUse0) - fImage0, fPalette0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0.tex, tex0Name, tex0Fmt, imageKey0) + imageKey0, fImage0, fPalette0 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name) if loadPal0: if fPalette0 is not None: paletteKey0 = fImage0.paletteKey else: - fPalette0, paletteKey0 = saveOrGetPaletteDefinition( - fMaterial, fModel, imUse0, tex0Name, tex0Fmt, pal0Fmt) + paletteKey0, fPalette0 = saveOrGetPaletteDefinition( + fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, pal0Len) fImage0.paletteKey = paletteKey0 if useTex1: - imageKey1 = getImageKey(f3dMat.tex1, imUse1) - fImage1, fPalette1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1.tex, tex1Name, tex1Fmt, imageKey1) + imageKey1, fImage1, fPalette1 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name) if useSharedCIPalette: fImage1.paletteKey = paletteKey0 if loadPal1: if fPalette1 is not None: paletteKey1 = fImage1.paletteKey else: - fPalette1, paletteKey1 = saveOrGetPaletteDefinition( - fMaterial, fModel, imUse1, tex1Name, tex1Fmt, pal1Fmt) + paletteKey1, fPalette1 = saveOrGetPaletteDefinition( + fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, pal1Len) fImage1.paletteKey = paletteKey1 - # Write texture and palette data. This will be unset for exporting as PNGs. - if convertTextureData: - TODO() - # Write DL entries to load textures and palettes loadGfx = fMaterial.material if loadPal0: @@ -1822,7 +1826,26 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) saveTextureTile( fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) - + + # Write texture and palette data, unless exporting textures as PNGs. + if convertTextureData: + if not isTex0Ref: + if loadPal0: + writePaletteData(fPalette0, pal0) + if useTex0: + if isCI: + writeCITextureData(f3dMat.tex0.tex, fImage0, pal0, pal0Fmt, tex0Fmt) + else: + writeNonCITextureData(f3dMat.tex0.tex, fImage0, tex0Fmt) + if not isTex1Ref: + if loadPal1: + writePaletteData(fPalette1, pal1) + if useTex1: + if isCI: + writeCITextureData(f3dMat.tex1.tex, fImage0, pal1, pal1Fmt, tex1Fmt) + else: + writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) + # Used so we know how to convert normalized UVs when saving verts. if imageDims0 is not None and imageDims1 is not None: if f3dMat.uv_basis == "TEXEL0": @@ -1921,6 +1944,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): return fMaterial, texDimensions +# Functions for texture and palette definitions def getTextureName(texProp: TextureProperty, fModelName: str, overrideName: str) -> str: tex = texProp.tex @@ -1959,50 +1983,101 @@ def getTextureNameTexRef(texProp: TextureProperty, fModelName: str) -> str: return texName -def extractConvertCIPixel(image, pixels, i, j, palFormat): - color = [1, 1, 1, 1] - for field in range(image.channels): - color[field] = pixels[(j * image.size[0] + i) * image.channels + field] - if palFormat == "RGBA16": - pixelColor = getRGBA16Tuple(color) - elif palFormat == "IA16": - pixelColor = getIA16Tuple(color) - else: - raise PluginError("Internal error with palette format") - return pixelColor +def checkDuplicateTextureName(fModelOrTexRect, name): + names = [] + for info, texture in fModelOrTexRect.textures.items(): + names.append(texture.name) + while name in names: + name = name + "_copy" + return name -def getColorsUsedInImage(image, palFormat): - palette = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - if pixelColor not in palette: - palette.append(pixelColor) - return palette +def saveOrGetPaletteDefinition( + fMaterial: FMaterial, + fModel: FModel, + texProp: TextureProperty, + images: list[bpy.types.Image], + imageName: str, + palLen: int, +) -> tuple[FPaletteKey, FImage]: + texFmt = texProp.tex_format + palFmt = texProp.ci_format + palFormat = texFormatOf[palFmt] + paletteKey = FPaletteKey(palFmt, images) -def mergePalettes(pal0, pal1): - palette = [c for c in pal0] - for c in pal1: - if c not in palette: - palette.append(c) - return palette + # If palette already loaded, return that data. + fPalette, _ = fModel.getTextureAndHandleShared(paletteKey) + if fPalette is not None: + # print(f"Palette already exists") + return paletteKey, fPalette + if texProp.use_tex_reference: + fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None, False) + return paletteKey, fPalette -def getColorIndicesOfTexture(image, palette, palFormat): - texture = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - texture.append(palette.index(pixelColor)) - return texture + paletteName = checkDuplicateTextureName(fModel, toAlnum(imageName) + "_pal_" + palFmt.lower()) + paletteFilename = getNameFromPath(imageName, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".pal" + fPalette = FImage( + paletteName, + palFormat, + "G_IM_SIZ_16b", + 1, + palLen, + paletteFilename, + False, + ) + + fModel.addTexture(paletteKey, fPalette, fMaterial) + return paletteKey, fPalette + + +def saveOrGetTextureDefinition( + fMaterial: FMaterial, + fModel: FModel, + texProp: TextureProperty, + images: list[bpy.types.Image], + imageName: str, +) -> tuple[FImageKey, FImage, FImage]: + image = texProp.tex + texFmt = texProp.tex_format + texFormat = texFormatOf[texFmt] + bitSize = texBitSizeOf[texFmt] + imageKey = getImageKey(texProp, imUse0) + + # If image already loaded, return that data. + fImage, fPalette = fModel.getTextureAndHandleShared(imageKey) + if fImage is not None: + # print(f"Image already exists") + return imageKey, fImage, fPalette + + if texProp.use_tex_reference: + width, height = texProp.tex_reference_size + fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) + return imageKey, fImage, None + + name = image.name if image.filepath == "" else image.filepath + filename = getNameFromPath(name, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".inc.c" + + fImage = FImage( + checkDuplicateTextureName(fModel, toAlnum(imageName)), + texFormat, + bitSize, + image.size[0], + image.size[1], + filename, + False, + ) + + if fMaterial.useLargeTextures: + fImage.isLargeTexture = True + + fModel.addTexture(imageKey, fImage, fMaterial) + return imageKey, fImage, None + + def getAndCheckTexInfo( propName: str, fModel: FModel, @@ -2081,8 +2156,9 @@ def getAndCheckTexInfo( pal, palLen ) - - + +# Functions for writing texture and palette DLs + def getTileSizeSettings(texProp: TextureProperty, tileSettings, f3d: F3D): if tileSettings is not None: SL = tileSettings.sl @@ -2272,183 +2348,50 @@ def savePaletteLoad( ] ) +# Functions for converting and writing texture and palette data -def todo(): - if isCITexture: - if texProp.use_tex_reference: - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - fPalette = FImage(texProp.pal_reference, None, None, 1, texProp.pal_reference_size, None, False) - +def extractConvertCIPixel(image, pixels, i, j, palFormat): + color = [1, 1, 1, 1] + for field in range(image.channels): + color[field] = pixels[(j * image.size[0] + i) * image.channels + field] + if palFormat == "RGBA16": + pixelColor = getRGBA16Tuple(color) + elif palFormat == "IA16": + pixelColor = getIA16Tuple(color) else: - if texProp.use_tex_reference: - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - - - -def saveOrGetPaletteDefinition( - fMaterial: FMaterial, - fModel: FModel, - images: list[bpy.types.Image], - imageName: str, - texFmt: str, - palFmt: str, -) -> tuple[FImage, tuple[bpy.types.Image, tuple[str, str]]]: - - palFormat = texFormatOf[palFmt] - paletteName = checkDuplicateTextureName(fModel, toAlnum(imageName) + "_pal_" + palFmt.lower()) - paletteKey = FPaletteKey(palFmt, images) - paletteFilename = getNameFromPath(imageName, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".pal" - - fPalette = FImage( - paletteName, - palFormat, - "G_IM_SIZ_16b", - 1, - len(palette), - paletteFilename, - False, - ) - - fModel.addTexture(paletteKey, fPalette, fMaterial) - return fPalette, paletteKey - - -def saveOrGetTextureDefinition( - fMaterial: FMaterial, - fModel: FModel, - image: bpy.types.Image, - imageName: str, - texFmt: str, - imageKey: FImageKey, -) -> tuple[FImage, FImage, bool]: - - texFormat = texFormatOf[texFmt] - bitSize = texBitSizeOf[texFmt] - - # If image already loaded, return that data. - fImage, fPalette = fModel.getTextureAndHandleShared(imageKey) - if fImage is not None: - # print(f"Image already exists") - return fImage, fPalette - - name = image.name if image.filepath == "" else image.filepath - filename = getNameFromPath(name, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".inc.c" - - fImage = FImage( - checkDuplicateTextureName(fModel, toAlnum(imageName)), - texFormat, - bitSize, - image.size[0], - image.size[1], - filename, - False, - ) - - if fMaterial.useLargeTextures: - fImage.isLargeTexture = True - - fModel.addTexture(imageKey, fImage, fMaterial) - - return fImage, None + raise PluginError("Internal error with palette format") + return pixelColor -def writePaletteData( - TODO, - palette: list[int], -): - for color in palette: - fPalette.data.extend(color.to_bytes(2, "big")) - fPalette.converted = True +def getColorsUsedInImage(image, palFormat): + palette = [] + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + if pixelColor not in palette: + palette.append(pixelColor) + return palette +def mergePalettes(pal0, pal1): + palette = [c for c in pal0] + for c in pal1: + if c not in palette: + palette.append(c) + return palette -def saveOrGetPaletteAndImageDefinition( - fMaterial, - fModelOrTexRect, - image, - imageName, - texFmt, - palFmt, - convertTextureData, - sharedPalette: FSharedPalette, - imageKey: FImageKey, -) -> tuple[FImage, FImage, bool]: - texFormat = texFormatOf[texFmt] - palFormat = texFormatOf[palFmt] - bitSize = texBitSizeOf[texFmt] - # If image already loaded, return that data. - fImage, fPalette = fModelOrTexRect.getTextureAndHandleShared(imageKey) - if fImage is not None: - # print(f"Image already exists") - return fImage, fPalette, True - # print(f"Size: {str(image.size[0])} x {str(image.size[1])}, Data: {str(len(image.pixels))}") - if sharedPalette is not None: - palette = sharedPalette.palette - else: - palette = [] +def getColorIndicesOfTexture(image, palette, palFormat): texture = [] - maxColors = 16 if bitSize == "G_IM_SIZ_4b" else 256 - if convertTextureData: - palette.extend(getColorsUsedInImage(image, palFmt)) - if len(palette) > maxColors: - raise PluginError( - "Texture " - + imageName - + " has more than " - + str(maxColors) - + " colors, or is part of a shared palette with too many colors." - ) - texture = getColorIndicesOfTexture(image, palette, palFmt) - - if image.filepath == "": - name = image.name - else: - name = image.filepath - filename = getNameFromPath(name, True) + "." + fModelOrTexRect.getTextureSuffixFromFormat(texFmt) + ".inc.c" - - # paletteFilename = getNameFromPath(name, True) + '.' + \ - # fModelOrTexRect.getTextureSuffixFromFormat(palFmt) + '.inc.c' - fImage = FImage( - checkDuplicateTextureName(fModelOrTexRect, toAlnum(imageName)), - texFormat, - bitSize, - image.size[0], - image.size[1], - filename, - convertTextureData, - ) - - if fMaterial.useLargeTextures: - fImage.isLargeTexture = True - - # paletteImage = bpy.data.images.new(paletteName, 1, len(palette)) - # paletteImage.pixels = palette - # paletteImage.filepath = paletteFilename - - if convertTextureData: - if bitSize == "G_IM_SIZ_4b": - fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) - else: - fImage.data = bytearray(texture) - - fModelOrTexRect.addTexture(imageKey, fImage, fMaterial) - - # For shared palettes, paletteName should be the same for the same imageName until - # the next saveOrGetPaletteOnlyDefinition - # Make sure paletteName is read here before saveOrGetPaletteOnlyDefinition is called. - paletteName = checkDuplicateTextureName(fModelOrTexRect, toAlnum(imageName) + "_pal_" + palFmt.lower()) - - if sharedPalette is None: - fPalette, paletteKey = saveOrGetPaletteOnlyDefinition( - fMaterial, fModelOrTexRect, [image], imageName, texFmt, palFmt, convertTextureData, palette - ) - fImage.paletteKey = paletteKey - else: - fPalette = None - fImage.paletteKey = None - - return fImage, fPalette, False # , paletteImage + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + texture.append(palette.index(pixelColor)) + return texture def compactNibbleArray(texture, width, height): @@ -2463,223 +2406,231 @@ def compactNibbleArray(texture, width, height): return bytearray(nibbleData) -def checkDuplicateTextureName(fModelOrTexRect, name): - names = [] - for info, texture in fModelOrTexRect.textures.items(): - names.append(texture.name) - while name in names: - name = name + "_copy" - return name +def writePaletteData(fPalette: FPalette, palette: list[int]): + for color in palette: + fPalette.data.extend(color.to_bytes(2, "big")) + fPalette.converted = True -def TODO_saveTextureData(fMaterial, fModel, image: bpy.types.Image, imageName, texFormat, convertTextureData): - fmt = texFormatOf[texFormat] - bitSize = texBitSizeOf[texFormat] +def writeCITextureData( + image: bpy.types.Image, + fImage: FImage, + palette: list[int], + palFmt: str, + texFmt: str, +): + palFormat = texFormatOf[palFmt] + bitSize = texBitSizeOf[texFmt] + + texture = getColorIndicesOfTexture(image, palette, palFormat) + + if bitSize == "G_IM_SIZ_4b": + fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) + else: + fImage.data = bytearray(texture) + fImage.converted = True + - pixels = image.pixels[:] - # print(f"Converting texture data for {filename}") - if fmt == "G_IM_FMT_RGBA": - if bitSize == "G_IM_SIZ_16b": - # fImage.data = bytearray([byteVal for doubleByte in [ - # (((int(image.pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F) & 0x1F) << 11) | \ - # ((int(image.pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F) & 0x1F) << 6) | \ - # ((int(image.pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F) & 0x1F) << 1) | \ - # (1 if image.pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - # ).to_bytes(2, 'big') - # for j in reversed(range(image.size[1])) for i in range(image.size[0])] for byteVal in doubleByte]) +def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): + fmt = texFormatOf[texFmt] + bitSize = texBitSizeOf[texFmt] - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ + pixels = image.pixels[:] + if fmt == "G_IM_FMT_RGBA": + if bitSize == "G_IM_SIZ_16b": + fImage.data = bytearray( + [ + byteVal + for doubleByte in [ + ( ( ( - ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) - << 3 - ) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) - >> 2 - ) - ), + (int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) + << 3 + ) + | ( + (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) + >> 2 + ) + ), + ( ( - ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) - << 6 - ) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) - << 1 - ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - ), - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) - elif bitSize == "G_IM_SIZ_32b": - fImage.data = bytearray( - [ - int(round(pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF + (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) + << 6 + ) + | ( + (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) + << 1 + ) + | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) + ), + ) for j in reversed(range(image.size[1])) for i in range(image.size[0]) - for field in range(image.channels) ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + for byteVal in doubleByte + ] + ) + elif bitSize == "G_IM_SIZ_32b": + fImage.data = bytearray( + [ + int(round(pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + for field in range(image.channels) + ] + ) + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - elif fmt == "G_IM_FMT_YUV": - raise PluginError("YUV not yet implemented.") - if bitSize == "G_IM_SIZ_16b": - pass - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + elif fmt == "G_IM_FMT_YUV": + raise PluginError("YUV not yet implemented.") + if bitSize == "G_IM_SIZ_16b": + pass + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - elif fmt == "G_IM_FMT_CI": - raise PluginError("CI not yet implemented.") + elif fmt == "G_IM_FMT_CI": + raise PluginError("CI not yet implemented.") - elif fmt == "G_IM_FMT_IA": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ + elif fmt == "G_IM_FMT_IA": + if bitSize == "G_IM_SIZ_4b": + fImage.data = bytearray( + [ + ( ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0x7 + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] ) + * 0x7 ) - & 0x7 ) - << 1 + & 0x7 ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ + << 1 + ) + | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_8b": + fImage.data = bytearray( + [ + ( ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xF + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] ) + * 0xF ) - & 0xF ) - << 4 + & 0xF ) - | (int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_16b": - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xFF + << 4 + ) + | (int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_16b": + fImage.data = bytearray( + [ + byteVal + for doubleByte in [ + ( + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] ) + * 0xFF ) - & 0xFF, - int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF, - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - elif fmt == "G_IM_FMT_I": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xF ) + & 0xFF, + int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF, ) - & 0xF for j in reversed(range(image.size[1])) for i in range(image.size[0]) ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xFF + for byteVal in doubleByte + ] + ) + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + elif fmt == "G_IM_FMT_I": + if bitSize == "G_IM_SIZ_4b": + fImage.data = bytearray( + [ + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] ) + * 0xF ) - & 0xFF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + ) + & 0xF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_8b": + fImage.data = bytearray( + [ + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] + ) + * 0xFF + ) + ) + & 0xFF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) else: - raise PluginError("Invalid image format " + fmt) - - # We stored 4bit values in byte arrays, now to convert - if bitSize == "G_IM_SIZ_4b": - fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + else: + raise PluginError("Invalid image format " + fmt) + # We stored 4bit values in byte arrays, now to convert + if bitSize == "G_IM_SIZ_4b": + fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) + + fImage.converted = True def saveLightsDefinition(fModel, fMaterial, material, lightsName): From d4df077d6e06d6b60cc2f7c83641c19823a89015 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 11 Dec 2022 12:34:36 -0800 Subject: [PATCH 07/38] Draft of texture saving changes --- fast64_internal/f3d/f3d_gbi.py | 10 +- fast64_internal/f3d/f3d_writer.py | 259 ++++++++++++++++++------------ 2 files changed, 161 insertions(+), 108 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 2dfe29144..32420a52a 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2819,9 +2819,13 @@ def __init__(self, name, DLFormat): # Used for tile scrolling self.tileSizeCommands = {} # dict of {texIndex : DPSetTileSize} - self.useLargeTextures = False - self.largeTextureIndex = None - self.texturesLoaded = [False, False] + # For saveMeshWithLargeTexturesByFaces + self.largeTexFmt = None + self.isTexLarge = [False, False] + self.largeTexAddr = [0, 0] + self.largeTexWords = 0 + self.imageKey = [None, None] + self.texPaletteIndex = [0, 0] def getScrollData(self, material, dimensions): self.getScrollDataField(material, 0, 0) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 4c6ee7c0c..edb5256d9 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -260,16 +260,15 @@ def findUVBounds(polygon, uv_data): class TileLoad: - def __init__(self, texFormat, twoTextures, texDimensions): + def __init__(self, texFormat, tmemWordsAvail, texDimensions): self.sl = None self.sh = None self.tl = None self.th = None self.texFormat = texFormat - self.twoTextures = twoTextures + self.tmemWordsAvail = tmemWordsAvail self.texDimensions = texDimensions - self.tmemMax = getTmemMax(texFormat) def getLow(self, value): return int(max(math.floor(value), 0)) @@ -291,9 +290,9 @@ def appendTile(self, sl, sh, tl, th): newWidth = abs(new_sl - new_sh) + 1 newHeight = abs(new_tl - new_th) + 1 - tmemUsage = getTmemWordUsage(self.texFormat, newWidth, newHeight) * 8 * (2 if self.twoTextures else 1) + tmemUsage = getTmemWordUsage(self.texFormat, newWidth, newHeight) - if tmemUsage > self.tmemMax: + if tmemUsage > self.tmemWordsAvail: return False else: self.sl = new_sl @@ -362,22 +361,17 @@ def saveMeshWithLargeTexturesByFaces( uv_data = obj.data.uv_layers["UVMap"].data convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) - if fMaterial.largeTextureIndex == 0: - texFormat = f3dMat.tex0.tex_format - otherTex = f3dMat.tex1 - otherTextureIndex = 1 - else: - texFormat = f3dMat.tex1.tex_format - otherTex = f3dMat.tex0 - otherTextureIndex = 0 + if fMaterial.imageKey[0] is not None: + fImage0, _ = fModel.getTextureAndHandleShared(fMaterial.imageKey[0]) + if fMaterial.imageKey[1] is not None: + fImage1, _ = fModel.getTextureAndHandleShared(fMaterial.imageKey[1]) - twoTextures = fMaterial.texturesLoaded[0] and fMaterial.texturesLoaded[1] tileLoads = {} faceTileLoads = {} for face in faces: uvs = [UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled) for loopIndex in face.loops] - faceTileLoad = TileLoad(texFormat, twoTextures, texDimensions) + faceTileLoad = TileLoad(fMaterial.largeTexFmt, fMaterial.largeTexWords, texDimensions) faceTileLoads[face] = faceTileLoad if not faceTileLoad.tryAdd(uvs): raise PluginError( @@ -402,60 +396,49 @@ def saveMeshWithLargeTexturesByFaces( triGroup = fMesh.tri_group_new(fMaterial) fMesh.draw.commands.append(SPDisplayList(triGroup.triList)) - # For materials with tex0 and tex1, if the other texture can fit into a single tile load, - # we load it once at the beginning only. - otherTexSingleLoad = False - if fMaterial.texturesLoaded[otherTextureIndex]: - tmem = getTmemWordUsage(otherTex.tex_format, otherTex.tex.size[0], otherTex.tex.size[1]) * 8 - if tmem <= getTmemMax(otherTex.tex_format): - otherTexSingleLoad = True - - # saveGeometry(obj, triList, fMesh.vertexList, bFaces, - # bMesh, texDimensions, transformMatrix, isPointSampled, isFlatShaded, - # exportVertexColors, fModel.f3d) currentGroupIndex = None - imageKey0, imageKey1 = getImageKeys(f3dMat, False) for tileLoad, tileFaces in tileLoads: revertCommands = GfxList("temp", GfxListTag.Draw, fModel.DLFormat) - nextTmem = 0 triGroup.triList.commands.append(DPPipeSync()) - if fMaterial.texturesLoaded[0] and not (otherTextureIndex == 0 and otherTexSingleLoad): - texDimensions0, nextTmem, fImage0 = saveTextureIndex( - material.name, - fModel, + if fMaterial.isTexLarge[0]: + saveTextureLoadOnly( + fImage0, + triGroup.triList, + f3dMat.tex0, + tileLoad, + 7, + fMaterial.largeTexAddr[0], + fModel.f3d) + saveTextureTile( + fImage0, fMaterial, triGroup.triList, - revertCommands, f3dMat.tex0, + tileLoad, 0, - nextTmem, - None, - False, - [tileLoad, None], - True, - False, - None, - imageKey0, - ) - if fMaterial.texturesLoaded[1] and not (otherTextureIndex == 1 and otherTexSingleLoad): - texDimensions1, nextTmem, fImage1 = saveTextureIndex( - material.name, - fModel, + fMaterial.largeTexAddr[0], + fMaterial.texPaletteIndex[0], + fModel.f3d) + if fMaterial.isTexLarge[1]: + saveTextureLoadOnly( + fImage1, + triGroup.triList, + f3dMat.tex1, + tileLoad, + 6, + fMaterial.largeTexAddr[1], + fModel.f3d) + saveTextureTile( + fImage1, fMaterial, triGroup.triList, - revertCommands, f3dMat.tex1, + tileLoad, 1, - nextTmem, - None, - False, - [None, tileLoad], - True, - False, - None, - imageKey1, - ) - + fMaterial.largeTexAddr[1], + fMaterial.texPaletteIndex[1], + fModel.f3d) + triConverter = TriangleConverter( triConverterInfo, texDimensions, @@ -519,7 +502,7 @@ def saveStaticModel( checkForF3dMaterialInFaces(obj, material) fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - if fMaterial.useLargeTextures: + if fMaterial.isTexLarge[0] or fMaterial.isTexLarge[1]: saveMeshWithLargeTexturesByFaces( material, faces, @@ -1751,46 +1734,125 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): pal0Len = len(pal0) tex0PaletteIndex = 1 useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 + fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] - # Handle same texture in both slots (only load once) - tex1ActuallyLoad = True - tex1Addr = tex0Tmem - tmemOccupied = tex0Tmem + tex1Tmem - if ( - useTex0 and useTex1 and ( - ( - not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex - ) or ( - isTex0Ref - and isTex1Ref - and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference - ) + # Assign TMEM addresses + sameTextures = useTex0 and useTex1 and ( + ( + not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex + ) or ( + isTex0Ref + and isTex1Ref + and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference ) - ): + ) + useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures + tmemSize = 256 if isCI else 512 + doTex0Load = doTex0Tile = doTex1Load = doTex1Tile = True + tex1Addr = None # must be set whenever tex 1 used (and loaded or tiled) + tmemOccupied = texDimensions = None # must be set on all codepaths + if sameTextures: assert tex0Tmem == tex1Tmem - tex1ActuallyLoad = False tmemOccupied = tex0Tmem - - useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures - fMaterial.useLargeTextures = useLargeTextures and (useTex0 or useTex1) - fMaterial.texturesLoaded[0] = useTex0 - fMaterial.texturesLoaded[1] = useTex1 - if not (bpy.context.scene.ignoreTextureRestrictions or useLargeTextures): - if tex0Tmem + tex1Tmem > (512 if not isCI else 256): + doTex1Load = False + tex1Addr = 0 + texDimensions = imageDims0 + fMaterial.largeTexFmt = tex0Fmt + elif not useLargeTextures or tex0Tmem + tex1Tmem <= tmemSize: + tex1Addr = tex0Tmem + tmemOccupied = tex0Tmem + tex1Tmem + if not useTex0 and not useTex1: + texDimensions = [32, 32] + fMaterial.largeTexFmt = "RGBA16" + elif not useTex1 or f3dMat.uv_basis == "TEXEL0": + texDimensions = imageDims0 + fMaterial.largeTexFmt = tex0Fmt + else: + texDimensions = imageDims1 + fMaterial.largeTexFmt = tex1Fmt + else: # useLargeTextures + if useTex0 and useTex1: + tmemOccupied = tmemSize + # TODO: Could change this in the future to do the face tile assigments + # first, to see how large a tile the large texture(s) needed, instead + # of arbitrarily assigning half of TMEM to each of the two textures. + if tex0Tmem <= tmemSize // 2: + # Tex 0 normal, tex 1 large + texDimensions = imageDims1 + fMaterial.largeTexFmt = tex1Fmt + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[1] = tex0Tmem + fMaterial.largeTexWords = tmemSize - tex0Tmem + doTex1Load = doTex1Tile = False + elif tex1Tmem <= tmemSize // 2: + # Tex 0 large, tex 1 normal + texDimensions = imageDims0 + fMaterial.largeTexFmt = tex0Fmt + fMaterial.isTexLarge[0] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexWords = tmemSize - tex1Tmem + doTex0Load = doTex0Tile = False + tex1Addr = tmemSize - tex1Tmem + else: + # Both textures large + if f3dMat.uv_basis == "TEXEL0": + texDimensions = imageDims0 + fMaterial.largeTexFmt = tex0Fmt + else: + texDimensions = imageDims1 + fMaterial.largeTexFmt = tex1Fmt + fMaterial.isTexLarge[0] = True + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexAddr[1] = tmemSize // 2 + fMaterial.largeTexWords = tmemSize // 2 + doTex0Load = doTex0Tile = doTex1Load = doTex1Tile = False + elif useTex0: + texDimensions = imageDims0 + fMaterial.largeTexFmt = tex0Fmt + if tex0Tmem > tmemSize: + fMaterial.isTexLarge[0] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexWords = tmemSize + doTex0Load = doTex0Tile = False + tmemOccupied = tmemSize + else: + tmemOccupied = tex0Tmem + elif useTex1: + tex1Addr = 0 + texDimensions = imageDims1 + fMaterial.largeTexFmt = tex1Fmt + if tex1Tmem > tmemSize: + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[1] = 0 + fMaterial.largeTexWords = tmemSize + doTex1Load = doTex1Tile = False + tmemOccupied = tmemSize + else: + tmemOccupied = tex1Tmem + if tmemOccupied > tmemSize: + if sameTextures and useLargeTextures: + raise PluginError( + 'Error in "' + + material.name + + '": Using the same texture for Tex0 and Tex1 is not compatible with large textures.' + ) + elif not bpy.context.scene.ignoreTextureRestrictions: raise PluginError( 'Error in "' + material.name + '": Textures are too big. Max TMEM size is 4k ' + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." ) - + # Get texture and palette definitions imUse0 = [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []) imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: imageKey0, fImage0, fPalette0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name) + fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, fMaterial.isTexLarge[0]) + fMaterial.imageKey[0] = imageKey0 if loadPal0: if fPalette0 is not None: paletteKey0 = fImage0.paletteKey @@ -1800,7 +1862,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): fImage0.paletteKey = paletteKey0 if useTex1: imageKey1, fImage1, fPalette1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name) + fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, fMaterial.isTexLarge[1]) + fMaterial.imageKey[1] = imageKey1 if useSharedCIPalette: fImage1.paletteKey = paletteKey0 if loadPal1: @@ -1815,15 +1878,16 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): loadGfx = fMaterial.material if loadPal0: savePaletteLoad(loadGfx, fPalette0, pal0Fmt, pal0Addr, pal0Len, 5, fModel.f3d) - if useTex0: + if useTex0 and doTex0Load: saveTextureLoadOnly(fImage0, loadGfx, f3dMat.tex0, None, 7, 0, fModel.f3d) + if useTex0 and doTex0Tile: saveTextureTile( fImage0, fMaterial, loadGfx, f3dMat.tex0, None, 0, 0, tex0PaletteIndex, fModel.f3d) if loadPal1: savePaletteLoad(loadGfx, fPalette1, pal1Fmt, pal1Addr, pal1Len, 4, fModel.f3d) - if useTex1: - if tex1ActuallyLoad: - saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) + if useTex1 and doTex1Load: + saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) + if useTex1 and doTex1Tile: saveTextureTile( fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) @@ -1847,21 +1911,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) # Used so we know how to convert normalized UVs when saving verts. - if imageDims0 is not None and imageDims1 is not None: - if f3dMat.uv_basis == "TEXEL0": - texDimensions = imageDims0 - fMaterial.largeTextureIndex = 0 - else: - texDimensions = imageDims1 - fMaterial.largeTextureIndex = 1 - elif texDimensions0 is not None: - texDimensions = imageDims0 - fMaterial.largeTextureIndex = 0 - elif texDimensions1 is not None: - texDimensions = imageDims1 - fMaterial.largeTextureIndex = 1 - else: - texDimensions = [32, 32] + nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: @@ -2039,6 +2089,7 @@ def saveOrGetTextureDefinition( texProp: TextureProperty, images: list[bpy.types.Image], imageName: str, + isLarge: bool, ) -> tuple[FImageKey, FImage, FImage]: image = texProp.tex @@ -2070,9 +2121,7 @@ def saveOrGetTextureDefinition( filename, False, ) - - if fMaterial.useLargeTextures: - fImage.isLargeTexture = True + fImage.isLargeTexture = isLarge fModel.addTexture(imageKey, fImage, fMaterial) return imageKey, fImage, None From 7bfc073ed21bac80be4182cffdce021c9e8006c5 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 11 Dec 2022 15:54:51 -0800 Subject: [PATCH 08/38] Converted exportTexRectCommon --- fast64_internal/f3d/f3d_gbi.py | 3 - fast64_internal/f3d/f3d_parser.py | 2 +- fast64_internal/f3d/f3d_writer.py | 207 ++++++++++------------- fast64_internal/oot/oot_model_classes.py | 6 - fast64_internal/sm64/sm64_f3d_writer.py | 40 +++-- fast64_internal/utility.py | 6 + 6 files changed, 114 insertions(+), 150 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 32420a52a..76edbe0d2 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2188,9 +2188,6 @@ def __init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod): def onMaterialCommandsBuilt(self, fMaterial, material, drawLayer): return - def getTextureSuffixFromFormat(self, texFmt): - return texFmt.lower() - def getDrawLayerV3(self, obj): return None diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index 4cf30f714..19f768588 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -1023,7 +1023,7 @@ def setTLUTMode(self, flags): texProp.ci_format = "IA16" elif flags == self.f3d.G_TT_RGBA16: texProp.ci_format = "RGBA16" - else: # self.f3d.G_TT_NONE or the unsupported value of 1 + else: # self.f3d.G_TT_NONE or the unsupported value of 1 if texProp.tex_format[:2] == "CI": texProp.tex_format = "RGBA16" diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index edb5256d9..383057e26 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -46,9 +46,11 @@ def __eq__(self, __o: object) -> bool: and self.imagesSharingPalette == __o.imagesSharingPalette ) + def getImageKey(texProp: TextureProperty, useList) -> FImageKey: return FImageKey(texProp.tex, texProp.tex_format, texProp.ci_format, useList) + class FPaletteKey: def __init__(self, palFormat: str, imagesSharingPalette: list[bpy.types.Image] = []): self.palFormat = palFormat @@ -402,13 +404,8 @@ def saveMeshWithLargeTexturesByFaces( triGroup.triList.commands.append(DPPipeSync()) if fMaterial.isTexLarge[0]: saveTextureLoadOnly( - fImage0, - triGroup.triList, - f3dMat.tex0, - tileLoad, - 7, - fMaterial.largeTexAddr[0], - fModel.f3d) + fImage0, triGroup.triList, f3dMat.tex0, tileLoad, 7, fMaterial.largeTexAddr[0], fModel.f3d + ) saveTextureTile( fImage0, fMaterial, @@ -418,16 +415,12 @@ def saveMeshWithLargeTexturesByFaces( 0, fMaterial.largeTexAddr[0], fMaterial.texPaletteIndex[0], - fModel.f3d) + fModel.f3d, + ) if fMaterial.isTexLarge[1]: saveTextureLoadOnly( - fImage1, - triGroup.triList, - f3dMat.tex1, - tileLoad, - 6, - fMaterial.largeTexAddr[1], - fModel.f3d) + fImage1, triGroup.triList, f3dMat.tex1, tileLoad, 6, fMaterial.largeTexAddr[1], fModel.f3d + ) saveTextureTile( fImage1, fMaterial, @@ -437,8 +430,9 @@ def saveMeshWithLargeTexturesByFaces( 1, fMaterial.largeTexAddr[1], fMaterial.texPaletteIndex[1], - fModel.f3d) - + fModel.f3d, + ) + triConverter = TriangleConverter( triConverterInfo, texDimensions, @@ -1521,18 +1515,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): pal0Fmt, tex0Name, imageDims0, - tileDims0, tex0Tmem, pal0, pal0Len, - ) = getAndCheckTexInfo( - material.name, - fModel, - f3dMat.tex0, - None, - None, - useDict["Texture 0"] - ) + ) = getAndCheckTexInfo(material.name, f3dMat.tex0, useDict["Texture 0"]) ( useTex1, isTex1Ref, @@ -1540,19 +1526,11 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Fmt, pal1Fmt, imageDims1, - tileDims1, tex1Tmem, pal1, pal1Len, - ) = getAndCheckTexInfo( - material.name, - fModel, - f3dMat.tex1, - None, - None, - useDict["Texture 1"] - ) - + ) = getAndCheckTexInfo(material.name, f3dMat.tex1, useDict["Texture 1"]) + if useTex0 and useTex1: if isTex0CI != isTex1CI: raise PluginError( @@ -1568,9 +1546,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): and f3dMat.tex0.tex_reference_size != f3dMat.tex1.tex_reference_size ): raise PluginError( - "In material " - + material.name - + ": Two textures with the same reference must have the same size." + "In material " + material.name + ": Two textures with the same reference must have the same size." ) if isCI: if pal0Fmt != pal1Fmt: @@ -1590,7 +1566,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + material.name + ": Two textures with the same palette reference must have the same palette size." ) - + isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) palFormat = pal0Fmt if useTex0 else pal1Fmt g_tt = "G_TT_NONE" if not isCI else ("G_TT_" + palFormat) @@ -1606,7 +1582,9 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): saveGeoModeDefinitionF3DEX2(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) else: saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) - saveOtherModeHDefinition(fMaterial, f3dMat.rdp_settings, g_tt, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod) + saveOtherModeHDefinition( + fMaterial, f3dMat.rdp_settings, g_tt, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod + ) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1644,7 +1622,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): loadPal0 = loadPal1 = True tex1PaletteIndex = 1 pal1Addr = 16 - else: # Two CI textures, normal mode + else: # Two CI textures, normal mode if tex0Fmt == "CI8" and tex1Fmt == "CI8": if isTex0Ref != isTex0Ref: raise PluginError( @@ -1671,7 +1649,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + str(pal0Len) + " colors, which can't fit in a CI8 palette (256)." ) - elif tex0Fmt != tex1Fmt: # One CI8, one CI4 + elif tex0Fmt != tex1Fmt: # One CI8, one CI4 ci8Pal, ci4Pal = pal0, pal1 if tex0Fmt == "CI8" else pal1, pal0 ci8PalLen, ci4PalLen = pal0Len, pal1Len if tex0Fmt == "CI8" else pal1Len, pal0Len if isTex0Ref or isTex1Ref: @@ -1703,7 +1681,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + " The CI8 texture must contain up to 240 unique colors," + " plus the same up to 16 colors used in the CI4 texture." ) - else: # both CI4 textures + else: # both CI4 textures if isTex0Ref and isTex1Ref and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: loadPal0 = True elif isTex0Ref or isTex1Ref: @@ -1735,22 +1713,21 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex0PaletteIndex = 1 useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] - + # Assign TMEM addresses - sameTextures = useTex0 and useTex1 and ( - ( - not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex - ) or ( - isTex0Ref - and isTex1Ref - and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference + sameTextures = ( + useTex0 + and useTex1 + and ( + (not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex) + or (isTex0Ref and isTex1Ref and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference) ) ) useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures tmemSize = 256 if isCI else 512 doTex0Load = doTex0Tile = doTex1Load = doTex1Tile = True - tex1Addr = None # must be set whenever tex 1 used (and loaded or tiled) - tmemOccupied = texDimensions = None # must be set on all codepaths + tex1Addr = None # must be set whenever tex 1 used (and loaded or tiled) + tmemOccupied = texDimensions = None # must be set on all codepaths if sameTextures: assert tex0Tmem == tex1Tmem tmemOccupied = tex0Tmem @@ -1770,7 +1747,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: texDimensions = imageDims1 fMaterial.largeTexFmt = tex1Fmt - else: # useLargeTextures + else: # useLargeTextures if useTex0 and useTex1: tmemOccupied = tmemSize # TODO: Could change this in the future to do the face tile assigments @@ -1844,25 +1821,28 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + '": Textures are too big. Max TMEM size is 4k ' + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." ) - + # Get texture and palette definitions imUse0 = [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []) imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: imageKey0, fImage0, fPalette0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, fMaterial.isTexLarge[0]) + fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, fMaterial.isTexLarge[0] + ) fMaterial.imageKey[0] = imageKey0 if loadPal0: if fPalette0 is not None: paletteKey0 = fImage0.paletteKey else: paletteKey0, fPalette0 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, pal0Len) + fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, pal0Len + ) fImage0.paletteKey = paletteKey0 if useTex1: imageKey1, fImage1, fPalette1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, fMaterial.isTexLarge[1]) + fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, fMaterial.isTexLarge[1] + ) fMaterial.imageKey[1] = imageKey1 if useSharedCIPalette: fImage1.paletteKey = paletteKey0 @@ -1871,9 +1851,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): paletteKey1 = fImage1.paletteKey else: paletteKey1, fPalette1 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, pal1Len) + fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, pal1Len + ) fImage1.paletteKey = paletteKey1 - + # Write DL entries to load textures and palettes loadGfx = fMaterial.material if loadPal0: @@ -1881,15 +1862,13 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): if useTex0 and doTex0Load: saveTextureLoadOnly(fImage0, loadGfx, f3dMat.tex0, None, 7, 0, fModel.f3d) if useTex0 and doTex0Tile: - saveTextureTile( - fImage0, fMaterial, loadGfx, f3dMat.tex0, None, 0, 0, tex0PaletteIndex, fModel.f3d) + saveTextureTile(fImage0, fMaterial, loadGfx, f3dMat.tex0, None, 0, 0, tex0PaletteIndex, fModel.f3d) if loadPal1: savePaletteLoad(loadGfx, fPalette1, pal1Fmt, pal1Addr, pal1Len, 4, fModel.f3d) if useTex1 and doTex1Load: saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) if useTex1 and doTex1Tile: - saveTextureTile( - fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) + saveTextureTile(fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) # Write texture and palette data, unless exporting textures as PNGs. if convertTextureData: @@ -1909,9 +1888,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): writeCITextureData(f3dMat.tex1.tex, fImage0, pal1, pal1Fmt, tex1Fmt) else: writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) - - # Used so we know how to convert normalized UVs when saving verts. - nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: @@ -1994,8 +1970,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): return fMaterial, texDimensions + # Functions for texture and palette definitions + def getTextureName(texProp: TextureProperty, fModelName: str, overrideName: str) -> str: tex = texProp.tex texFormat = texProp.tex_format @@ -2033,9 +2011,9 @@ def getTextureNameTexRef(texProp: TextureProperty, fModelName: str) -> str: return texName -def checkDuplicateTextureName(fModelOrTexRect, name): +def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): names = [] - for info, texture in fModelOrTexRect.textures.items(): + for info, texture in parent.textures.items(): names.append(texture.name) while name in names: name = name + "_copy" @@ -2044,7 +2022,7 @@ def checkDuplicateTextureName(fModelOrTexRect, name): def saveOrGetPaletteDefinition( fMaterial: FMaterial, - fModel: FModel, + parent: Union[FModel, FTexRect], texProp: TextureProperty, images: list[bpy.types.Image], imageName: str, @@ -2057,7 +2035,7 @@ def saveOrGetPaletteDefinition( paletteKey = FPaletteKey(palFmt, images) # If palette already loaded, return that data. - fPalette, _ = fModel.getTextureAndHandleShared(paletteKey) + fPalette, _ = parent.getTextureAndHandleShared(paletteKey) if fPalette is not None: # print(f"Palette already exists") return paletteKey, fPalette @@ -2066,8 +2044,8 @@ def saveOrGetPaletteDefinition( fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None, False) return paletteKey, fPalette - paletteName = checkDuplicateTextureName(fModel, toAlnum(imageName) + "_pal_" + palFmt.lower()) - paletteFilename = getNameFromPath(imageName, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".pal" + paletteName = checkDuplicateTextureName(parent, toAlnum(imageName) + "_pal_" + palFmt.lower()) + paletteFilename = getNameFromPath(imageName, True) + "." + getTextureSuffixFromFormat(texFmt) + ".pal" fPalette = FImage( paletteName, @@ -2079,27 +2057,27 @@ def saveOrGetPaletteDefinition( False, ) - fModel.addTexture(paletteKey, fPalette, fMaterial) + parent.addTexture(paletteKey, fPalette, fMaterial) return paletteKey, fPalette def saveOrGetTextureDefinition( fMaterial: FMaterial, - fModel: FModel, + parent: Union[FModel, FTexRect], texProp: TextureProperty, images: list[bpy.types.Image], imageName: str, isLarge: bool, ) -> tuple[FImageKey, FImage, FImage]: - + image = texProp.tex texFmt = texProp.tex_format texFormat = texFormatOf[texFmt] bitSize = texBitSizeOf[texFmt] imageKey = getImageKey(texProp, imUse0) - + # If image already loaded, return that data. - fImage, fPalette = fModel.getTextureAndHandleShared(imageKey) + fImage, fPalette = parent.getTextureAndHandleShared(imageKey) if fImage is not None: # print(f"Image already exists") return imageKey, fImage, fPalette @@ -2108,12 +2086,12 @@ def saveOrGetTextureDefinition( width, height = texProp.tex_reference_size fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) return imageKey, fImage, None - + name = image.name if image.filepath == "" else image.filepath - filename = getNameFromPath(name, True) + "." + fModel.getTextureSuffixFromFormat(texFmt) + ".inc.c" + filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texFmt) + ".inc.c" fImage = FImage( - checkDuplicateTextureName(fModel, toAlnum(imageName)), + checkDuplicateTextureName(parent, toAlnum(imageName)), texFormat, bitSize, image.size[0], @@ -2123,21 +2101,18 @@ def saveOrGetTextureDefinition( ) fImage.isLargeTexture = isLarge - fModel.addTexture(imageKey, fImage, fMaterial) + parent.addTexture(imageKey, fImage, fMaterial) return imageKey, fImage, None def getAndCheckTexInfo( propName: str, - fModel: FModel, texProp: TextureProperty, - overrideName: str, - tileSettings, useDictEntry, ): if not useDictEntry or not texProp.tex_set: - return False, False, False, "", "", (0, 0), (0, 0), 0, None, 0 - + return False, False, False, "", "", (0, 0), 0, None, 0 + tex = texProp.tex isTexRef = texProp.use_tex_reference texFormat = texProp.tex_format @@ -2163,15 +2138,10 @@ def getAndCheckTexInfo( imageWidth, imageHeight = texProp.tex_reference_size else: imageWidth, imageHeight = tex.size - - if tileSettings is not None: - tileWidth, tileHeight = tileSettings.getDimensions() - else: - tileWidth, tileHeight = imageWidth, imageHeight - tmemSize = getTmemWordUsage(texFormat, tileWidth, tileHeight) + tmemSize = getTmemWordUsage(texFormat, imageWidth, imageHeight) - if tileWidth > 1024 or tileHeight > 1024: + if imageWidth > 1024 or imageHeight > 1024: raise PluginError('Error in "' + propName + '": Any side of an image cannot be greater ' + "than 1024.") pal = None @@ -2192,7 +2162,7 @@ def getAndCheckTexInfo( + texFormat + "." ) - + return ( True, isTexRef, @@ -2200,14 +2170,15 @@ def getAndCheckTexInfo( texFormat, palFormat, (imageWidth, imageHeight), - (tileWidth, tileHeight), tmemSize, pal, - palLen + palLen, ) + # Functions for writing texture and palette DLs + def getTileSizeSettings(texProp: TextureProperty, tileSettings, f3d: F3D): if tileSettings is not None: SL = tileSettings.sl @@ -2278,8 +2249,10 @@ def saveTextureLoadOnly( ) else: if useLoadBlock: - dxs = (((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) - >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"]) - 1 + dxs = ( + ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) + >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] + ) - 1 dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) gfxOut.commands.extend( [ @@ -2341,9 +2314,7 @@ def saveTextureTile( tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) gfxOut.commands.extend( [ - DPSetTile( - fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts - ), + DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts), tileSizeCommand, ] ) # added in) @@ -2357,7 +2328,7 @@ def saveTextureTile( # palLen is the number of colors def savePaletteLoad( gfxOut: GfxList, - fPalette: FPalette, + fPalette: FImage, palFormat: str, palAddr: int, palLen: int, @@ -2397,8 +2368,10 @@ def savePaletteLoad( ] ) + # Functions for converting and writing texture and palette data + def extractConvertCIPixel(image, pixels, i, j, palFormat): color = [1, 1, 1, 1] for field in range(image.channels): @@ -2455,7 +2428,7 @@ def compactNibbleArray(texture, width, height): return bytearray(nibbleData) -def writePaletteData(fPalette: FPalette, palette: list[int]): +def writePaletteData(fPalette: FImage, palette: list[int]): for color in palette: fPalette.data.extend(color.to_bytes(2, "big")) fPalette.converted = True @@ -2470,9 +2443,9 @@ def writeCITextureData( ): palFormat = texFormatOf[palFmt] bitSize = texBitSizeOf[texFmt] - + texture = getColorIndicesOfTexture(image, palette, palFormat) - + if bitSize == "G_IM_SIZ_4b": fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) else: @@ -2493,20 +2466,14 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): for doubleByte in [ ( ( - ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) - << 3 - ) + ((int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3) | ( (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) >> 2 ) ), ( - ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) - << 6 - ) + ((int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6) | ( (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) << 1 @@ -2635,9 +2602,7 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): round( colorToLuminance( pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels + (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels + 3 ] ) @@ -2656,9 +2621,7 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): round( colorToLuminance( pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels + (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels + 3 ] ) @@ -2678,7 +2641,7 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): # We stored 4bit values in byte arrays, now to convert if bitSize == "G_IM_SIZ_4b": fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) - + fImage.converted = True diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 4ef5e73d0..7e67f516d 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -128,12 +128,6 @@ def getRenderMode(self, drawLayer): cycle2 = getattr(defaultRenderModes, drawLayerUsed.lower() + "Cycle2") return [cycle1, cycle2] - def getTextureSuffixFromFormat(self, texFmt): - if texFmt == "RGBA16": - return "rgb5a1" - else: - return texFmt.lower() - def modifyDLForCIFlipbook(self, fMaterial: FMaterial, fPalette: FMaterial, texProp: TextureProperty): raise PluginError("TODO: modifyDLForCIFlipbook has been broken by sync and DPSetTextureLUT changes") # Modfiy DL to use new palette texture diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 1f9dec979..42129e0a5 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -4,7 +4,14 @@ from mathutils import Matrix, Vector from bpy.utils import register_class, unregister_class from ..panels import SM64_Panel -from ..f3d.f3d_writer import saveTextureIndex, exportF3DCommon +from ..f3d.f3d_writer import ( + getAndCheckTexInfo, + saveOrGetTextureDefinition, + saveTextureLoadOnly, + saveTextureTile, + writeNonCITextureData, + exportF3DCommon, +) from ..f3d.f3d_material import TextureProperty, tmemUsageUI, all_combiner_uses, ui_procAnim from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup from .sm64_utility import starSelectWarning @@ -357,23 +364,20 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): drawEndCommands = GfxList("temp", GfxListTag.Draw, DLFormat.Dynamic) - texDimensions, nextTmem, fImage = saveTextureIndex( - texProp.tex.name, - fTexRect, - fMaterial, - fTexRect.draw, - drawEndCommands, - texProp, - 0, - 0, - "texture", - convertTextureData, - None, - True, - True, - None, - FImageKey(texProp.tex, texProp.tex_format, texProp.ci_format, [texProp.tex]), - ) + useTex, isTexRef, isTexCI, texFmt, _, texName, imageDims, texTmem, _, _ = getAndCheckTexInfo(name, texProp, True) + if not useTex: + raise PluginError("In " + name + ": texture disabled.") + if isTexCI: + raise PluginError("In " + name + ": CI textures not compatible with exportTexRectCommon (b/c copy mode).") + if texTmem > 512: + raise PluginError("In " + name + ": texture is too big (> 4 KiB).") + if texFmt != "RGBA16": + raise PluginError("In " + name + ": texture format must be RGBA16 (b/c copy mode).") + imageKey, fImage, _ = saveOrGetTextureDefinition(fMaterial, fTexRect, texProp, [tex], texName, False) + saveTextureLoadOnly(fImage, fTexRect.draw, texProp, None, 7, 0, fTexRect.f3d) + saveTextureTile(fImage, fMaterial, fTexRect.draw, texProp, None, 0, 0, 0, fTexRect.f3d) + if convertTextureData: + writeNonCITextureData(tex, fImage, texFmt) fTexRect.draw.commands.append( SPScisTextureRectangle(0, 0, (texDimensions[0] - 1) << 2, (texDimensions[1] - 1) << 2, 0, 0, 0) diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 5cf28a03c..7b167e9f1 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -1483,3 +1483,9 @@ def ootGetBaseOrCustomLight(prop, idx, toExport: bool, errIfMissing: bool): if toExport: col, dir = exportColor(col), normToSigned8Vector(dir) return col, dir + + +def getTextureSuffixFromFormat(texFmt): + # if texFmt == "RGBA16": + # return "rgb5a1" + return texFmt.lower() From 99e3d1d73648923bf8902b2e658405eb1162c45f Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 11 Dec 2022 16:53:52 -0800 Subject: [PATCH 09/38] Converting flipbook --- fast64_internal/f3d/f3d_writer.py | 2 +- fast64_internal/oot/oot_model_classes.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 12a0aa5fd..198cc4d01 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -2076,7 +2076,7 @@ def saveOrGetTextureDefinition( texFmt = texProp.tex_format texFormat = texFormatOf[texFmt] bitSize = texBitSizeOf[texFmt] - imageKey = getImageKey(texProp, imUse0) + imageKey = getImageKey(texProp, images) # If image already loaded, return that data. fImage, fPalette = parent.getTextureAndHandleShared(imageKey) diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index acc49c34f..eedbede38 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -279,13 +279,13 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M if flipbookProp.exportMode == "Individual" else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) - fImage = saveOrGetTextureDefinition( + _, fImage, _ = saveOrGetTextureDefinition( fMaterial, model, - flipbookTexture.image, + texProp, + [flipbookTexture.image], name, - texProp.tex_format, - True, + False, ) # do this here to check for modified names due to repeats From a404a578ac961813174e08d789597519c8cf6a1e Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 11 Dec 2022 22:27:01 -0800 Subject: [PATCH 10/38] Converting flipbook --- fast64_internal/f3d/f3d_gbi.py | 29 ++++++- fast64_internal/f3d/f3d_writer.py | 60 ++++++++++----- fast64_internal/f3d/flipbook.py | 1 + fast64_internal/oot/oot_model_classes.py | 98 ++++++++++-------------- fast64_internal/sm64/sm64_f3d_writer.py | 2 +- 5 files changed, 110 insertions(+), 80 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 62fc07eb6..867bdcaa3 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2189,17 +2189,38 @@ def processTexRefNonCITextures( ): """ For non CI textures that use a texture reference, process additional textures that will possibly be loaded here. - This doesn't return anything. + Returns an object containing info about the additional textures, or None. + """ + return None + + def writeTexRefNonCITextures(self, obj, texFmt: str): + """ + Write data for non-CI textures which were previously processed. """ pass def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Material, index: int) -> "FImage": """ For CI textures that use a texture reference, process additional textures that will possibly be loaded here. - This returns a palette FImage that is shared between all processed textures. + Returns: + - an object containing info about the additional textures, or None + - the palette to use (or None) + - the palette name (or None) """ - texProp = getattr(material.f3dMat, f"tex{index}") - return FImage(texProp.pal_reference, None, None, 1, texProp.pal_reference_size, None, False) + return None, None, None + + def writeTexRefCITextures( + self, + obj, + paletteKey: FPaletteKey, + pal: list[int], + texFmt: str, + palFmt: str, + ): + """ + Write data for CI textures which were previously processed. + """ + pass # Called before SPEndDisplayList def onMaterialCommandsBuilt(self, fMaterial, material, drawLayer): diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 198cc4d01..79fa0ddb7 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1518,7 +1518,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex0Tmem, pal0, pal0Len, - ) = getAndCheckTexInfo(material.name, f3dMat.tex0, useDict["Texture 0"]) + tex0Flipbook, + ) = getAndCheckTexInfo(0, fModel, fMaterial, material, f3dMat.tex0, useDict["Texture 0"]) ( useTex1, isTex1Ref, @@ -1529,7 +1530,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Tmem, pal1, pal1Len, - ) = getAndCheckTexInfo(material.name, f3dMat.tex1, useDict["Texture 1"]) + tex1Flipbook, + ) = getAndCheckTexInfo(1, fModel, fMaterial, material, f3dMat.tex1, useDict["Texture 1"]) if useTex0 and useTex1: if isTex0CI != isTex1CI: @@ -1598,10 +1600,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: fMaterial.material.commands.append(SPTexture(s, t, 0, fModel.f3d.G_TX_RENDERTILE, 1)) - TODO() - fPalette = fModel.processTexRefCITextures(fMaterial, material, index) - fModel.processTexRefNonCITextures(fMaterial, material, index) - # Determine how to arrange / load palette entries into upper half of tmem tex0PaletteIndex = 0 tex1PaletteIndex = 0 @@ -1628,14 +1626,15 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): pal1Addr = 16 else: # Two CI textures, normal mode if tex0Fmt == "CI8" and tex1Fmt == "CI8": - if isTex0Ref != isTex0Ref: + if (pal0 is None) != (pal1 is None): raise PluginError( "In material " + material.name - + ": can't have two CI8 textures where only one is a reference; no way to assign the palette." + + ": can't have two CI8 textures where only one is a non-flipbook reference; " + + "no way to assign the palette." ) loadPal0 = True - if isTex0Ref: + if pal0 is None: if f3dMat.tex0.pal_reference != f3dMat.tex1.pal_reference: raise PluginError( "In material " @@ -1656,7 +1655,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): elif tex0Fmt != tex1Fmt: # One CI8, one CI4 ci8Pal, ci4Pal = pal0, pal1 if tex0Fmt == "CI8" else pal1, pal0 ci8PalLen, ci4PalLen = pal0Len, pal1Len if tex0Fmt == "CI8" else pal1Len, pal0Len - if isTex0Ref or isTex1Ref: + if pal0 is None or pal1 is None: if ci8PalLen > 256 - 16: raise PluginError( "In material " @@ -1686,9 +1685,9 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + " plus the same up to 16 colors used in the CI4 texture." ) else: # both CI4 textures - if isTex0Ref and isTex1Ref and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: + if pal0 is None and pal1 is None and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: loadPal0 = True - elif isTex0Ref or isTex1Ref: + elif pal0 is None or pal1 is None: loadPal0 = loadPal1 = True tex1PaletteIndex = 1 pal1Addr = 16 @@ -1827,6 +1826,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): ) # Get texture and palette definitions + TODO() # Get the full list of textures from the flipbook imUse0 = [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []) imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] fImage0 = fImage1 = fPalette0 = fPalette1 = None @@ -2108,12 +2108,15 @@ def saveOrGetTextureDefinition( def getAndCheckTexInfo( - propName: str, + index: int, + fModel: FModel, + fMaterial: FMaterial, + material: bpy.types.Material, texProp: TextureProperty, useDictEntry, ): if not useDictEntry or not texProp.tex_set: - return False, False, False, "", "", (0, 0), 0, None, 0 + return False, False, False, "", "", "", (0, 0), 0, None, 0, None tex = texProp.tex isTexRef = texProp.use_tex_reference @@ -2128,14 +2131,14 @@ def getAndCheckTexInfo( if not isTexRef: if tex is None: - raise PluginError("In " + propName + ", no texture is selected.") + raise PluginError(f"In {material.name}, no texture is selected.") elif len(tex.pixels) == 0: raise PluginError( "Could not load missing texture: " + tex.name + ". Make sure this texture has not been deleted or moved on disk." ) - + if isTexRef: imageWidth, imageHeight = texProp.tex_reference_size else: @@ -2144,14 +2147,22 @@ def getAndCheckTexInfo( tmemSize = getTmemWordUsage(texFormat, imageWidth, imageHeight) if imageWidth > 1024 or imageHeight > 1024: - raise PluginError('Error in "' + propName + '": Any side of an image cannot be greater ' + "than 1024.") + raise PluginError(f'Error in "{material.name}": Any side of an image cannot be greater ' + "than 1024.") + + texName = TODO() # tex.name? pal = None palLen = 0 if isCITexture: + flipbook, pal, palName = fModel.processTexRefCITextures(fMaterial, material, index) if isTexRef: - palLen = texProp.pal_reference_size + if flipbook is not None: + palLen = len(pal) + texName = palName + else: + palLen = texProp.pal_reference_size else: + assert flipbook is None pal = getColorsUsedInImage(tex, palFormat) palLen = len(pal0) if palLen > (16 if texFormat == "CI4" else 256): @@ -2159,11 +2170,14 @@ def getAndCheckTexInfo( "In " + propName + ", texture " - + tex.name + + texName + + (" (all flipbook textures)" if flipbook is not None else "") + " uses too many unique colors to fit in format" + texFormat + "." ) + else: + flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) return ( True, @@ -2171,10 +2185,12 @@ def getAndCheckTexInfo( isCITexture, texFormat, palFormat, + texName, (imageWidth, imageHeight), tmemSize, pal, palLen, + flipbook, ) @@ -2431,6 +2447,8 @@ def compactNibbleArray(texture, width, height): def writePaletteData(fPalette: FImage, palette: list[int]): + if fPalette.converted: + return for color in palette: fPalette.data.extend(color.to_bytes(2, "big")) fPalette.converted = True @@ -2443,6 +2461,8 @@ def writeCITextureData( palFmt: str, texFmt: str, ): + if fImage.converted: + return palFormat = texFormatOf[palFmt] bitSize = texBitSizeOf[texFmt] @@ -2456,6 +2476,8 @@ def writeCITextureData( def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): + if fImage.converted: + return fmt = texFormatOf[texFmt] bitSize = texBitSizeOf[texFmt] diff --git a/fast64_internal/f3d/flipbook.py b/fast64_internal/f3d/flipbook.py index d0d5c6c1b..301ba0707 100644 --- a/fast64_internal/f3d/flipbook.py +++ b/fast64_internal/f3d/flipbook.py @@ -12,6 +12,7 @@ class TextureFlipbook: name: str exportMode: str textureNames: list[str] + images: list[tuple[bpy.types.Image, FImage]] def flipbook_data_to_c(flipbook: TextureFlipbook): diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index eedbede38..bb915ca3c 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -150,6 +150,10 @@ def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): def validateCIFlipbook( self, existingFPalette: FImage, alreadyExists: bool, fPalette: FImage, flipbookImage: bpy.types.Image ) -> Union[FImage, bool]: + # TODO: Not sure what all the logic is here. Handling repeated textures + # in the same flipbook should be trivial, and repeats of textures between + # different flipbooks should be handled separately because of the set of + # textures used in the image key. if existingFPalette is None: if alreadyExists: if fPalette: @@ -187,13 +191,14 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate texProp = getattr(material.f3d_mat, f"tex{index}") if not usesFlipbook(material, flipbookProp, index, True, ootFlipbookReferenceIsValid): return FModel.processTexRefCITextures(fMaterial, material, index) - if len(flipbookProp.textures) == 0: raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") + flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, []) - sharedPalette = FSharedPalette(model.name + "_" + flipbookProp.textures[0].image.name + "_pal") + palName = model.name + "_" + flipbookProp.textures[0].image.name + pal = [] existingFPalette = None - fImages = [] + allImages = [flipbookTexture.image for flipbookTexture in flipbookProp.textures] for flipbookTexture in flipbookProp.textures: if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") @@ -204,61 +209,42 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) - texName = getTextureNameTexRef(texProp, model.name) - # fPalette should be None here, since sharedPalette is not None - fImage, fPalette, alreadyExists = saveOrGetPaletteAndImageDefinition( - fMaterial, - model, - flipbookTexture.image, - name, - texProp.tex_format, - texProp.ci_format, - True, - sharedPalette, - FImageKey( - flipbookTexture.image, - texProp.tex_format, - texProp.ci_format, - [flipbookTexture.image for flipbookTexture in flipbookProp.textures], - ), + imageKey = FImageKey( + flipbookTexture.image, texProp.tex_format, texProp.ci_format, allImages ) + fImage, fPalette = parent.getTextureAndHandleShared(imageKey) + alreadyExists = fImage is not None + if not alreadyExists: + width, height = texProp.tex_reference_size + fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) existingFPalette = model.validateCIFlipbook( existingFPalette, alreadyExists, fPalette, flipbookTexture.image ) - fImages.append(fImage) - # do this here to check for modified names due to repeats + pal = mergePalettes(pal, getColorsUsedInImage(flipbookTexture.image, texProp.ci_format)) + flipbook.textureNames.append(fImage.name) + flipbook.images.append((flipbookTexture.image, fImage)) + # print(f"Palette length for {palName}: {len(pal)}") # Checked in getAndCheckTexInfo + model.addFlipbookWithRepeatCheck(flipbook) - - # print(f"Palette length for {sharedPalette.name}: {len(sharedPalette.palette)}") firstImage = flipbookProp.textures[0].image - model.processedFlipbooks[firstImage] = [flipbookTex.image for flipbookTex in flipbookProp.textures] - - if existingFPalette == False: - - palFormat = texProp.ci_format - fPalette, paletteKey = saveOrGetPaletteOnlyDefinition( - fMaterial, - model, - [tex.image for tex in flipbookProp.textures], - sharedPalette.name, - texProp.tex_format, - palFormat, - True, - sharedPalette.palette, - ) + model.processedFlipbooks[firstImage] = allImages - # using the first image for the key, apply paletteKey to all images - # while this is not ideal, its better to us an image for the key as - # names are modified when duplicates are found - for fImage in fImages: - fImage.paletteKey = paletteKey - else: - fPalette = existingFPalette + return flipbook, pal, palName - return fPalette + def writeTexRefCITextures( + self, + flipbook: TextureFlipbook, + paletteKey: FPaletteKey, + pal: list[int], + texFmt: str, + palFmt: str, + ): + for image, fImage in flipbook.images: + fImage.paletteKey = paletteKey + writeCITextureData(image, fImage, pal, texFmt, palFmt) def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int): model = self.getFlipbookOwner() @@ -269,7 +255,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M if len(flipbookProp.textures) == 0: raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") - flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, []) + flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) for flipbookTexture in flipbookProp.textures: if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") @@ -280,17 +266,17 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) _, fImage, _ = saveOrGetTextureDefinition( - fMaterial, - model, - texProp, - [flipbookTexture.image], - name, - False, + fMaterial, model, texProp, [flipbookTexture.image], name, False, ) - - # do this here to check for modified names due to repeats flipbook.textureNames.append(fImage.name) + flipbook.images.append((flipbookTexture.image, fImage)) + self.addFlipbookWithRepeatCheck(flipbook) + return flipbook + + def writeTexRefNonCITextures(self, flipbook: TextureFlipbook, texFmt: str): + for image, fImage in flipbook.images: + writeNonCITextureData(image, fImage, texFmt) def onMaterialCommandsBuilt(self, fMaterial, material, drawLayer): # handle dynamic material calls diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 42129e0a5..05fbf5886 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -364,7 +364,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): drawEndCommands = GfxList("temp", GfxListTag.Draw, DLFormat.Dynamic) - useTex, isTexRef, isTexCI, texFmt, _, texName, imageDims, texTmem, _, _ = getAndCheckTexInfo(name, texProp, True) + useTex, isTexRef, isTexCI, texFmt, _, texName, imageDims, texTmem, _, _ = getAndCheckTexInfo(0, name, texProp, True) if not useTex: raise PluginError("In " + name + ": texture disabled.") if isTexCI: From dbaf27c0fc318d20d302cec0c67b00393581b8bb Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 13 Dec 2022 22:03:30 -0800 Subject: [PATCH 11/38] Finished draft of converting flipbook --- fast64_internal/f3d/f3d_gbi.py | 17 +++++++-- fast64_internal/f3d/f3d_writer.py | 46 +++++++++++------------- fast64_internal/oot/oot_model_classes.py | 25 +++++++------ 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 867bdcaa3..3a78d06ba 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2189,13 +2189,19 @@ def processTexRefNonCITextures( ): """ For non CI textures that use a texture reference, process additional textures that will possibly be loaded here. - Returns an object containing info about the additional textures, or None. + Returns: + - a list of images which are referenced (normally just the texture + image), for creating image / palette keys + - an object containing info about the additional textures, or None """ - return None + texProp = getattr(material.f3d_mat, f"tex{index}") + imUse = [] if texProp.tex is None else [texProp.tex] + return imUse, None def writeTexRefNonCITextures(self, obj, texFmt: str): """ Write data for non-CI textures which were previously processed. + obj is the object returned by processTexRefNonCITextures. """ pass @@ -2203,11 +2209,15 @@ def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Ma """ For CI textures that use a texture reference, process additional textures that will possibly be loaded here. Returns: + - a list of images which are referenced (normally just the texture + image), for creating image / palette keys - an object containing info about the additional textures, or None - the palette to use (or None) - the palette name (or None) """ - return None, None, None + texProp = getattr(material.f3d_mat, f"tex{index}") + imUse = [] if texProp.tex is None else [texProp.tex] + return imUse, None, None, None def writeTexRefCITextures( self, @@ -2219,6 +2229,7 @@ def writeTexRefCITextures( ): """ Write data for CI textures which were previously processed. + obj is the object returned by processTexRefCITextures. """ pass diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 79fa0ddb7..e5fc89e18 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1401,12 +1401,6 @@ def getTexDimensions(material): return texDimensions -class FSharedPalette: - def __init__(self, name): - self.name = name - self.palette = [] - - def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): if material.mat_ver > 3: f3dMat = material.f3d_mat @@ -1518,6 +1512,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex0Tmem, pal0, pal0Len, + imUse0, tex0Flipbook, ) = getAndCheckTexInfo(0, fModel, fMaterial, material, f3dMat.tex0, useDict["Texture 0"]) ( @@ -1530,6 +1525,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Tmem, pal1, pal1Len, + imUse1, tex1Flipbook, ) = getAndCheckTexInfo(1, fModel, fMaterial, material, f3dMat.tex1, useDict["Texture 1"]) @@ -1714,8 +1710,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): pal0 = pal1 + pal0 pal0Len = len(pal0) tex0PaletteIndex = 1 - useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] + useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 + pal0Name, pal1Name = tex0Name, tex1Name + if useSharedCIPalette: + imUse0 = imUse1 = imUse0 + imUse1 + pal0Name = getSharedPaletteName(f3dMat) # Assign TMEM addresses sameTextures = ( @@ -1826,9 +1826,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): ) # Get texture and palette definitions - TODO() # Get the full list of textures from the flipbook - imUse0 = [f3dMat.tex0.tex] + ([f3dMat.tex1.tex] if useSharedCIPalette else []) - imUse1 = ([f3dMat.tex0.tex] if useSharedCIPalette else []) + [f3dMat.tex1.tex] fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: imageKey0, fImage0, fPalette0 = saveOrGetTextureDefinition( @@ -1840,7 +1837,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): paletteKey0 = fImage0.paletteKey else: paletteKey0, fPalette0 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, pal0Len + fMaterial, fModel, f3dMat.tex0, imUse0, pal0Name, pal0Len ) fImage0.paletteKey = paletteKey0 if useTex1: @@ -1855,7 +1852,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): paletteKey1 = fImage1.paletteKey else: paletteKey1, fPalette1 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, pal1Len + fMaterial, fModel, f3dMat.tex1, imUse1, pal1Name, pal1Len ) fImage1.paletteKey = paletteKey1 @@ -1892,6 +1889,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): writeCITextureData(f3dMat.tex1.tex, fImage0, pal1, pal1Fmt, tex1Fmt) else: writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) + if isCI: + fModel.writeTexRefCITextures(tex0Flipbook, paletteKey0, pal0, tex0Fmt, pal0Fmt) + fModel.writeTexRefCITextures(tex1Flipbook, paletteKey1, pal1, tex1Fmt, pal1Fmt) + else: + fModel.writeTexRefNonCITextures(tex0Flipbook, tex0Fmt) + fModel.writeTexRefNonCITextures(tex1Flipbook, tex1Fmt) nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: @@ -2003,14 +2006,7 @@ def getSharedPaletteName(f3dMat: F3DMaterialProperty): texFormat = f3dMat.tex0.tex_format.lower() tex0Name = getNameFromPath(image0.filepath if image0.filepath != "" else image0.name, True) tex1Name = getNameFromPath(image1.filepath if image1.filepath != "" else image1.name, True) - return f"{tex0Name}_x_{tex1Name}_{texFormat}_pal" - - -def getTextureNameTexRef(texProp: TextureProperty, fModelName: str) -> str: - texFormat = texProp.tex_format - name = texProp.tex_reference - texName = fModelName + "_" + (getNameFromPath(name, True) + "_" + texFormat.lower()) - return texName + return f"{tex0Name}_x_{tex1Name}_{texFormat}" def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): @@ -2116,13 +2112,14 @@ def getAndCheckTexInfo( useDictEntry, ): if not useDictEntry or not texProp.tex_set: - return False, False, False, "", "", "", (0, 0), 0, None, 0, None + return False, False, False, "", "", "", (0, 0), 0, None, 0, None, None tex = texProp.tex isTexRef = texProp.use_tex_reference texFormat = texProp.tex_format isCITexture = texFormat[:2] == "CI" palFormat = texProp.ci_format if isCITexture else "" + texName = getTextureName(texProp, fModel.name, None) if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0): raise PluginError( @@ -2149,12 +2146,10 @@ def getAndCheckTexInfo( if imageWidth > 1024 or imageHeight > 1024: raise PluginError(f'Error in "{material.name}": Any side of an image cannot be greater ' + "than 1024.") - texName = TODO() # tex.name? - pal = None palLen = 0 if isCITexture: - flipbook, pal, palName = fModel.processTexRefCITextures(fMaterial, material, index) + imUse, flipbook, pal, palName = fModel.processTexRefCITextures(fMaterial, material, index) if isTexRef: if flipbook is not None: palLen = len(pal) @@ -2177,7 +2172,7 @@ def getAndCheckTexInfo( + "." ) else: - flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) + imUse, flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) return ( True, @@ -2190,6 +2185,7 @@ def getAndCheckTexInfo( tmemSize, pal, palLen, + imUse, flipbook, ) diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index bb915ca3c..0ebefa6ec 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -8,16 +8,14 @@ from ..f3d.f3d_writer import ( VertexGroupInfo, TriangleConverterInfo, - FSharedPalette, DPLoadTLUTCmd, DPSetTextureLUT, DPSetTile, FImageKey, + mergePalettes, saveOrGetTextureDefinition, - saveOrGetPaletteAndImageDefinition, - getTextureNameTexRef, - saveOrGetPaletteOnlyDefinition, - texFormatOf, + writeCITextureData, + writeNonCITextureData, ) from ..f3d.f3d_gbi import ( @@ -152,8 +150,8 @@ def validateCIFlipbook( ) -> Union[FImage, bool]: # TODO: Not sure what all the logic is here. Handling repeated textures # in the same flipbook should be trivial, and repeats of textures between - # different flipbooks should be handled separately because of the set of - # textures used in the image key. + # different flipbooks should be automatically handled separately because + # of the set of textures used in the image key. if existingFPalette is None: if alreadyExists: if fPalette: @@ -232,16 +230,18 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate firstImage = flipbookProp.textures[0].image model.processedFlipbooks[firstImage] = allImages - return flipbook, pal, palName + return allImages, flipbook, pal, palName def writeTexRefCITextures( self, - flipbook: TextureFlipbook, + flipbook: Union[TextureFlipbook, None], paletteKey: FPaletteKey, pal: list[int], texFmt: str, palFmt: str, ): + if flipbook is None: + return for image, fImage in flipbook.images: fImage.paletteKey = paletteKey writeCITextureData(image, fImage, pal, texFmt, palFmt) @@ -256,6 +256,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) + allImages = [flipbookTexture.image for flipbookTexture in flipbookProp.textures] for flipbookTexture in flipbookProp.textures: if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") @@ -272,9 +273,11 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M flipbook.images.append((flipbookTexture.image, fImage)) self.addFlipbookWithRepeatCheck(flipbook) - return flipbook + return allImages, flipbook - def writeTexRefNonCITextures(self, flipbook: TextureFlipbook, texFmt: str): + def writeTexRefNonCITextures(self, flipbook: Union[TextureFlipbook, None], texFmt: str): + if flipbook is None: + return for image, fImage in flipbook.images: writeNonCITextureData(image, fImage, texFmt) From 92396318dde9bbe449ded4c9ff0b5f650aaf4f1a Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 13 Dec 2022 22:05:50 -0800 Subject: [PATCH 12/38] blacked --- fast64_internal/f3d/f3d_gbi.py | 8 +++----- fast64_internal/f3d/f3d_writer.py | 2 +- fast64_internal/oot/oot_model_classes.py | 19 +++++++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 3a78d06ba..60797aa15 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2184,9 +2184,7 @@ def __init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod): self.global_data = FGlobalData() self.texturesSavedLastExport = 0 # hacky - def processTexRefNonCITextures( - self, fMaterial: "FMaterial", material: bpy.types.Material, index: int - ): + def processTexRefNonCITextures(self, fMaterial: "FMaterial", material: bpy.types.Material, index: int): """ For non CI textures that use a texture reference, process additional textures that will possibly be loaded here. Returns: @@ -2197,7 +2195,7 @@ def processTexRefNonCITextures( texProp = getattr(material.f3d_mat, f"tex{index}") imUse = [] if texProp.tex is None else [texProp.tex] return imUse, None - + def writeTexRefNonCITextures(self, obj, texFmt: str): """ Write data for non-CI textures which were previously processed. @@ -2218,7 +2216,7 @@ def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Ma texProp = getattr(material.f3d_mat, f"tex{index}") imUse = [] if texProp.tex is None else [texProp.tex] return imUse, None, None, None - + def writeTexRefCITextures( self, obj, diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index e5fc89e18..6e18b363c 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -2135,7 +2135,7 @@ def getAndCheckTexInfo( + tex.name + ". Make sure this texture has not been deleted or moved on disk." ) - + if isTexRef: imageWidth, imageHeight = texProp.tex_reference_size else: diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 0ebefa6ec..0cc05b134 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -207,9 +207,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) - imageKey = FImageKey( - flipbookTexture.image, texProp.tex_format, texProp.ci_format, allImages - ) + imageKey = FImageKey(flipbookTexture.image, texProp.tex_format, texProp.ci_format, allImages) fImage, fPalette = parent.getTextureAndHandleShared(imageKey) alreadyExists = fImage is not None if not alreadyExists: @@ -220,12 +218,12 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate ) pal = mergePalettes(pal, getColorsUsedInImage(flipbookTexture.image, texProp.ci_format)) - + flipbook.textureNames.append(fImage.name) flipbook.images.append((flipbookTexture.image, fImage)) # print(f"Palette length for {palName}: {len(pal)}") # Checked in getAndCheckTexInfo - + model.addFlipbookWithRepeatCheck(flipbook) firstImage = flipbookProp.textures[0].image model.processedFlipbooks[firstImage] = allImages @@ -267,14 +265,19 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) _, fImage, _ = saveOrGetTextureDefinition( - fMaterial, model, texProp, [flipbookTexture.image], name, False, + fMaterial, + model, + texProp, + [flipbookTexture.image], + name, + False, ) flipbook.textureNames.append(fImage.name) flipbook.images.append((flipbookTexture.image, fImage)) - + self.addFlipbookWithRepeatCheck(flipbook) return allImages, flipbook - + def writeTexRefNonCITextures(self, flipbook: Union[TextureFlipbook, None], texFmt: str): if flipbook is None: return From bc1352b35a58e0f36f03d57535b3cf2f2ccacd75 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 13 Dec 2022 22:57:08 -0800 Subject: [PATCH 13/38] Exporting mat with two CI4 textures seems to work --- fast64_internal/f3d/f3d_gbi.py | 2 +- fast64_internal/f3d/f3d_writer.py | 58 ++++++++++++++---------- fast64_internal/f3d/flipbook.py | 1 + fast64_internal/oot/oot_model_classes.py | 10 ++-- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 60797aa15..a5c5b034f 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2220,7 +2220,7 @@ def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Ma def writeTexRefCITextures( self, obj, - paletteKey: FPaletteKey, + paletteKey, pal: list[int], texFmt: str, palFmt: str, diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 6e18b363c..bb344f58d 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1521,6 +1521,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): isTex1CI, tex1Fmt, pal1Fmt, + tex1Name, imageDims1, tex1Tmem, pal1, @@ -1529,6 +1530,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Flipbook, ) = getAndCheckTexInfo(1, fModel, fMaterial, material, f3dMat.tex1, useDict["Texture 1"]) + isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) + if useTex0 and useTex1: if isTex0CI != isTex1CI: raise PluginError( @@ -1565,7 +1568,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + ": Two textures with the same palette reference must have the same palette size." ) - isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) palFormat = pal0Fmt if useTex0 else pal1Fmt g_tt = "G_TT_NONE" if not isCI else ("G_TT_" + palFormat) @@ -1873,28 +1875,38 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Write texture and palette data, unless exporting textures as PNGs. if convertTextureData: - if not isTex0Ref: - if loadPal0: - writePaletteData(fPalette0, pal0) - if useTex0: + if loadPal0 and ((not isTex0Ref) or (tex0Flipbook is not None)): + writePaletteData(fPalette0, pal0) + if useTex0: + if isTex0Ref: + if isCI: + fModel.writeTexRefCITextures(tex0Flipbook, paletteKey0, pal0, tex0Fmt, pal0Fmt) + else: + fModel.writeTexRefNonCITextures(tex0Flipbook, tex0Fmt) + else: if isCI: writeCITextureData(f3dMat.tex0.tex, fImage0, pal0, pal0Fmt, tex0Fmt) else: writeNonCITextureData(f3dMat.tex0.tex, fImage0, tex0Fmt) - if not isTex1Ref: - if loadPal1: - writePaletteData(fPalette1, pal1) - if useTex1: + if loadPal1 and ((not isTex1Ref) or (tex1Flipbook is not None and not useSharedCIPalette)): + writePaletteData(fPalette1, pal1) + if useTex1: + if isTex1Ref: if isCI: - writeCITextureData(f3dMat.tex1.tex, fImage0, pal1, pal1Fmt, tex1Fmt) + fModel.writeTexRefCITextures( + tex1Flipbook, + paletteKey0 if useSharedCIPalette else paletteKey1, + pal0 if useSharedCIPalette else pal1, + tex1Fmt, + pal1Fmt, + ) + else: + fModel.writeTexRefNonCITextures(tex1Flipbook, tex1Fmt) + else: + if isCI: + writeCITextureData(f3dMat.tex1.tex, fImage1, pal1, pal1Fmt, tex1Fmt) else: writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) - if isCI: - fModel.writeTexRefCITextures(tex0Flipbook, paletteKey0, pal0, tex0Fmt, pal0Fmt) - fModel.writeTexRefCITextures(tex1Flipbook, paletteKey1, pal1, tex1Fmt, pal1Fmt) - else: - fModel.writeTexRefNonCITextures(tex0Flipbook, tex0Fmt) - fModel.writeTexRefNonCITextures(tex1Flipbook, tex1Fmt) nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: @@ -2159,7 +2171,7 @@ def getAndCheckTexInfo( else: assert flipbook is None pal = getColorsUsedInImage(tex, palFormat) - palLen = len(pal0) + palLen = len(pal) if palLen > (16 if texFormat == "CI4" else 256): raise PluginError( "In " @@ -2354,7 +2366,7 @@ def savePaletteLoad( nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] if not f3d._HW_VERSION_1: - loadTexGfx.commands.extend( + gfxOut.commands.extend( [ DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), @@ -2362,7 +2374,7 @@ def savePaletteLoad( ] ) else: - loadTexGfx.commands.extend( + gfxOut.commands.extend( [ _DPLoadTextureBlock( fPalette, @@ -2395,7 +2407,7 @@ def extractConvertCIPixel(image, pixels, i, j, palFormat): elif palFormat == "IA16": pixelColor = getIA16Tuple(color) else: - raise PluginError("Internal error with palette format") + raise PluginError("Internal error, palette format is " + palFormat) return pixelColor @@ -2459,12 +2471,10 @@ def writeCITextureData( ): if fImage.converted: return - palFormat = texFormatOf[palFmt] - bitSize = texBitSizeOf[texFmt] - texture = getColorIndicesOfTexture(image, palette, palFormat) + texture = getColorIndicesOfTexture(image, palette, palFmt) - if bitSize == "G_IM_SIZ_4b": + if texFmt == "CI4": fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) else: fImage.data = bytearray(texture) diff --git a/fast64_internal/f3d/flipbook.py b/fast64_internal/f3d/flipbook.py index 301ba0707..5165ca7f8 100644 --- a/fast64_internal/f3d/flipbook.py +++ b/fast64_internal/f3d/flipbook.py @@ -2,6 +2,7 @@ from typing import Any, Callable, Optional from bpy.utils import register_class, unregister_class from bpy.app.handlers import persistent +from .f3d_gbi import FImage from .f3d_material import all_combiner_uses, update_tex_values_manual, iter_tex_nodes, TextureProperty from ..utility import prop_split, CollectionProperty from dataclasses import dataclass diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 0cc05b134..703e5e8ce 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -12,6 +12,7 @@ DPSetTextureLUT, DPSetTile, FImageKey, + FPaletteKey, mergePalettes, saveOrGetTextureDefinition, writeCITextureData, @@ -188,7 +189,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbookProp = getattr(material.flipbookGroup, f"flipbook{index}") texProp = getattr(material.f3d_mat, f"tex{index}") if not usesFlipbook(material, flipbookProp, index, True, ootFlipbookReferenceIsValid): - return FModel.processTexRefCITextures(fMaterial, material, index) + return super().processTexRefCITextures(fMaterial, material, index) if len(flipbookProp.textures) == 0: raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") @@ -239,7 +240,8 @@ def writeTexRefCITextures( palFmt: str, ): if flipbook is None: - return + return super().writeTexRefCITextures(None, paletteKey, pal, texFmt, palFmt) + assert paletteKey is not None for image, fImage in flipbook.images: fImage.paletteKey = paletteKey writeCITextureData(image, fImage, pal, texFmt, palFmt) @@ -249,7 +251,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M flipbookProp = getattr(material.flipbookGroup, f"flipbook{index}") texProp = getattr(material.f3d_mat, f"tex{index}") if not usesFlipbook(material, flipbookProp, index, True, ootFlipbookReferenceIsValid): - return FModel.processTexRefNonCITextures(self, fMaterial, material, index) + return super().processTexRefNonCITextures(fMaterial, material, index) if len(flipbookProp.textures) == 0: raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") @@ -280,7 +282,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M def writeTexRefNonCITextures(self, flipbook: Union[TextureFlipbook, None], texFmt: str): if flipbook is None: - return + return super().writeTexRefNonCITextures(flipbook, texFmt) for image, fImage in flipbook.images: writeNonCITextureData(image, fImage, texFmt) From f3b4895346a4bb42d55fd979d02da098106d98bd Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 25 Dec 2022 22:50:43 -0800 Subject: [PATCH 14/38] Draft working, not tested large textures or flipbooks yet --- fast64_internal/f3d/f3d_gbi.py | 193 ++++++++++------------- fast64_internal/f3d/f3d_writer.py | 95 ++++++----- fast64_internal/oot/oot_model_classes.py | 102 +++++------- 3 files changed, 173 insertions(+), 217 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index aa8a42749..c3b1e7c2b 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2220,7 +2220,8 @@ def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Ma def writeTexRefCITextures( self, obj, - paletteKey, + fMaterial: "FMaterial", + imagesSharingPalette: list[bpy.types.Image], pal: list[int], texFmt: str, palFmt: str, @@ -2283,41 +2284,22 @@ def onEndDraw(self, fMesh, contextObj): def getTextureAndHandleShared(self, imageKey): # Check if texture is in self if imageKey in self.textures: - fImage = self.textures[imageKey] - if fImage.paletteKey is not None: - if fImage.paletteKey in self.textures: - fPalette = self.textures[fImage.paletteKey] - else: - print(f"Can't find {str(fImage.paletteKey)}") - fPalette = None - else: - # print("Palette key is None") - fPalette = None - - return fImage, fPalette + return self.textures[imageKey] if self.parentModel is not None: # Check if texture is in parent if imageKey in self.parentModel.textures: - fImage = self.parentModel.textures[imageKey] - fPalette = self.parentModel.textures[fImage.paletteKey] if fImage.paletteKey is not None else None - return fImage, fPalette + return self.parentModel.textures[imageKey] # Check if texture is in siblings for subModel in self.parentModel.subModels: if imageKey in subModel.textures: fImage = subModel.textures.pop(imageKey) self.parentModel.textures[imageKey] = fImage - - paletteKey = fImage.paletteKey - fPalette = None - if paletteKey is not None: - fPalette = subModel.textures.pop(paletteKey) - self.parentModel.textures[paletteKey] = fPalette - return fImage, fPalette - return None, None + return fImage + return None else: - return None, None + return None def getLightAndHandleShared(self, lightName): # Check if light is in self @@ -2356,7 +2338,7 @@ def getMaterialAndHandleShared(self, materialKey): # If material is in sibling, handle the material's textures as well. for imageKey in materialItem[0].usedImages: - fImage, fPalette = self.getTextureAndHandleShared(imageKey) + fImage = self.getTextureAndHandleShared(imageKey) if fImage is None: raise PluginError("Error: If a material exists, its textures should exist too.") @@ -3090,7 +3072,7 @@ def spc(x): # A palette is just a RGBA16 texture with width = 1. class FImage: - def __init__(self, name, fmt, bitSize, width, height, filename, converted): + def __init__(self, name, fmt, bitSize, width, height, filename): self.name = name self.fmt = fmt self.bitSize = bitSize @@ -3099,9 +3081,8 @@ def __init__(self, name, fmt, bitSize, width, height, filename, converted): self.startAddress = 0 self.data = bytearray(0) self.filename = filename - self.converted = converted + self.converted = False self.isLargeTexture = False - self.paletteKey = None # another FImage reference def size(self): return len(self.data) @@ -3194,7 +3175,7 @@ def gsSPNoOp(f3d): # base class for gbi macros -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class GbiMacro: _segptrs = False _ptr_amp = False @@ -3227,7 +3208,7 @@ def size(self, f3d): return GFX_SIZE -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPMatrix(GbiMacro): matrix: int param: int @@ -3244,7 +3225,7 @@ def to_binary(self, f3d, segments): # Divide mesh drawing by materials into separate gfx -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPVertex(GbiMacro): # v = seg pointer, n = count, v0 = ? vertList: VtxList @@ -3281,7 +3262,7 @@ def to_c(self, static=True): return header + ", " + str(self.count) + ", " + str(self.index) + ")" -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPViewport(GbiMacro): # v = seg pointer, n = count, v0 = ? viewport: Vp @@ -3296,7 +3277,7 @@ def to_binary(self, f3d, segments): return gsDma1p(f3d.G_MOVEMEM, vpPtr, VP_SIZE, f3d.G_MV_VIEWPORT) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPDisplayList(GbiMacro): displayList: GfxList @@ -3317,7 +3298,7 @@ def to_c(self, static=True): return "glistp = " + self.displayList.name + "(glistp)" -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPBranchList(GbiMacro): displayList: GfxList _ptr_amp = True # add an ampersand to names @@ -3422,7 +3403,7 @@ def _gsSP1Quadrangle_w2f(v0, v1, v2, v3, flag): return _gsSP1Triangle_w1(v3, v1, v2) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SP1Triangle(GbiMacro): v0: int v1: int @@ -3438,7 +3419,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLine3D(GbiMacro): v0: int v1: int @@ -3452,7 +3433,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLineW3D(GbiMacro): v0: int v1: int @@ -3470,7 +3451,7 @@ def to_binary(self, f3d, segments): # SP1Quadrangle -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SP2Triangles(GbiMacro): v00: int v01: int @@ -3492,7 +3473,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPCullDisplayList(GbiMacro): vstart: int vend: int @@ -3505,7 +3486,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPSegment(GbiMacro): segment: int base: int @@ -3518,7 +3499,7 @@ def to_c(self, static=True): return header + str(self.segment) + ", " + "0x" + format(self.base, "X") + ")" -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPClipRatio(GbiMacro): ratio: int @@ -3542,7 +3523,7 @@ def size(self, f3d): # SPForceMatrix -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPModifyVertex(GbiMacro): vtx: int where: int @@ -3563,7 +3544,7 @@ def to_binary(self, f3d, segments): # SPBranchLessZ -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPBranchLessZraw(GbiMacro): dl: GfxList vtx: int @@ -3598,7 +3579,7 @@ def size(self, f3d): # SPDmaWrite -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPNumLights(GbiMacro): # n is macro name (string) n: str @@ -3607,7 +3588,7 @@ def to_binary(self, f3d, segments): return gsMoveWd(f3d.G_MW_NUMLIGHT, f3d.G_MWO_NUMLIGHT, f3d.NUML(self.n), f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLight(GbiMacro): # n is macro name (string) light: int # start address of light @@ -3623,7 +3604,7 @@ def to_binary(self, f3d, segments): return data -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLightColor(GbiMacro): # n is macro name (string) n: str @@ -3639,7 +3620,7 @@ def to_c(self, static=True): return header + str(self.n) + ", 0x" + format(self.col, "08X") + ")" -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPSetLights(GbiMacro): lights: Lights @@ -3702,7 +3683,7 @@ def gsSPLookAtY(l, f3d): return gsDma1p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LOOKATY) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLookAt(GbiMacro): la: LookAt _ptr_amp = True # add an ampersand to names @@ -3712,7 +3693,7 @@ def to_binary(self, f3d, segments): return gsSPLookAtX(light0Ptr, f3d) + gsSPLookAtY(light0Ptr + 16, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetHilite1Tile(GbiMacro): tile: int hilite: Hilite @@ -3730,7 +3711,7 @@ def to_binary(self, f3d, segments): ).to_binary(f3d, segments) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetHilite2Tile(GbiMacro): tile: int hilite: Hilite @@ -3748,7 +3729,7 @@ def to_binary(self, f3d, segments): ).to_binary(f3d, segments) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPFogFactor(GbiMacro): fm: int fo: int @@ -3778,7 +3759,7 @@ def to_c(self, static=True): return header + str(self.minVal) + ", " + str(self.maxVal) + ")" -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPTexture(GbiMacro): s: int t: int @@ -3810,7 +3791,7 @@ def to_binary(self, f3d, segments): # SPTextureL -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPPerspNormalize(GbiMacro): s: int @@ -3822,7 +3803,7 @@ def to_binary(self, f3d, segments): # SPPopMatrix -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPEndDisplayList(GbiMacro): def to_binary(self, f3d, segments): words = _SHIFTL(f3d.G_ENDDL, 24, 8), 0 @@ -3874,7 +3855,7 @@ def geoFlagListToWord(flagList, f3d): return word -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPGeometryMode(GbiMacro): clearFlagList: list setFlagList: list @@ -3889,7 +3870,7 @@ def to_binary(self, f3d, segments): raise PluginError("GeometryMode only available in F3DEX_GBI_2.") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPSetGeometryMode(GbiMacro): flagList: list @@ -3902,7 +3883,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPClearGeometryMode(GbiMacro): flagList: list @@ -3915,7 +3896,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPLoadGeometryMode(GbiMacro): flagList: list @@ -3935,7 +3916,7 @@ def gsSPSetOtherMode(cmd, sft, length, data, f3d): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPSetOtherMode(GbiMacro): cmd: str sft: int @@ -3951,7 +3932,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(cmd, sft, self.length, data, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPPipelineMode(GbiMacro): # mode is a string mode: str @@ -3964,7 +3945,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_PIPELINE, 1, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetCycleType(GbiMacro): # mode is a string mode: str @@ -3981,7 +3962,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_CYCLETYPE, 2, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTexturePersp(GbiMacro): # mode is a string mode: str @@ -3994,7 +3975,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTPERSP, 1, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureDetail(GbiMacro): # mode is a string mode: str @@ -4009,7 +3990,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTDETAIL, 2, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureLOD(GbiMacro): # mode is a string mode: str @@ -4022,7 +4003,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTLOD, 1, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureLUT(GbiMacro): # mode is a string mode: str @@ -4039,7 +4020,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTLUT, 2, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureFilter(GbiMacro): # mode is a string mode: str @@ -4054,7 +4035,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTFILT, 2, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureConvert(GbiMacro): # mode is a string mode: str @@ -4069,7 +4050,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_TEXTCONV, 3, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetCombineKey(GbiMacro): # mode is a string mode: str @@ -4082,7 +4063,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_COMBKEY, 1, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetColorDither(GbiMacro): # mode is a string mode: str @@ -4108,7 +4089,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_COLORDITHER, 1, modeVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetAlphaDither(GbiMacro): # mode is a string mode: str @@ -4128,7 +4109,7 @@ def to_binary(self, f3d, segments): raise PluginError("SetAlphaDither not available in HW v1.") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetAlphaCompare(GbiMacro): # mask is a string mode: str @@ -4143,7 +4124,7 @@ def to_binary(self, f3d, segments): return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, f3d.G_MDSFT_ALPHACOMPARE, 2, maskVal, f3d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetDepthSource(GbiMacro): # src is a string src: str @@ -4172,7 +4153,7 @@ def GBL_c2(m1a, m1b, m2a, m2b): return (m1a) << 28 | (m1b) << 24 | (m2a) << 20 | (m2b) << 16 -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetRenderMode(GbiMacro): # bl0-3 are string for each blender enum def __init__(self, flagList, blendList): @@ -4251,7 +4232,7 @@ def gsSetImage(cmd, fmt, siz, width, i): # DPSetDepthImage -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTextureImage(GbiMacro): fmt: str siz: str @@ -4294,7 +4275,7 @@ def GCCc1w1(sbRGB1, saA1, mA1, aRGB1, sbA1, aA1): ) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetCombineMode(GbiMacro): # all strings a0: str @@ -4347,7 +4328,7 @@ def sDPRGBColor(cmd, r, g, b, a): return gsDPSetColor(cmd, (_SHIFTL(r, 24, 8) | _SHIFTL(g, 16, 8) | _SHIFTL(b, 8, 8) | _SHIFTL(a, 0, 8))) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetEnvColor(GbiMacro): r: int g: int @@ -4358,7 +4339,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETENVCOLOR, self.r, self.g, self.b, self.a) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetBlendColor(GbiMacro): r: int g: int @@ -4369,7 +4350,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETBLENDCOLOR, self.r, self.g, self.b, self.a) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetFogColor(GbiMacro): r: int g: int @@ -4380,7 +4361,7 @@ def to_binary(self, f3d, segments): return sDPRGBColor(f3d.G_SETFOGCOLOR, self.r, self.g, self.b, self.a) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetFillColor(GbiMacro): d: int @@ -4388,7 +4369,7 @@ def to_binary(self, f3d, segments): return gsDPSetColor(f3d.G_SETFILLCOLOR, self.d) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetPrimDepth(GbiMacro): z: int = 0 dz: int = 0 @@ -4397,7 +4378,7 @@ def to_binary(self, f3d, segments): return gsDPSetColor(f3d.G_SETPRIMDEPTH, _SHIFTL(self.z, 16, 16) | _SHIFTL(self.dz, 0, 16)) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetPrimColor(GbiMacro): m: int l: int @@ -4413,7 +4394,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetOtherMode(GbiMacro): mode0: list mode1: list @@ -4430,7 +4411,7 @@ def gsDPLoadTileGeneric(c, tile, uls, ult, lrs, lrt): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTileSize(GbiMacro): t: int uls: int @@ -4445,7 +4426,7 @@ def is_LOADTILE(self, f3d): return self.t == f3d.G_TX_LOADTILE -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTile(GbiMacro): t: int uls: int @@ -4457,7 +4438,7 @@ def to_binary(self, f3d, segments): return gsDPLoadTileGeneric(f3d.G_LOADTILE, self.t, self.uls, self.ult, self.lrs, self.lrt) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetTile(GbiMacro): fmt: str siz: str @@ -4498,7 +4479,7 @@ def is_LOADTILE(self, f3d): return self.tile == f3d.G_TX_LOADTILE -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadBlock(GbiMacro): tile: int uls: int @@ -4515,7 +4496,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTLUTCmd(GbiMacro): tile: int count: int @@ -4525,7 +4506,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTextureBlock(GbiMacro): timg: FImage fmt: str @@ -4598,7 +4579,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTextureBlockYuv(GbiMacro): timg: FImage fmt: str @@ -4676,7 +4657,7 @@ def size(self, f3d): # gsDPLoadTextureBlockYuvS -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class _DPLoadTextureBlock(GbiMacro): timg: FImage tmem: int @@ -4755,7 +4736,7 @@ def size(self, f3d): # gsDPLoadMultiBlockS -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTextureBlock_4b(GbiMacro): timg: FImage fmt: str @@ -4826,7 +4807,7 @@ def size(self, f3d): # _gsDPLoadTextureBlock_4b -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTextureTile(GbiMacro): timg: FImage fmt: str @@ -4902,7 +4883,7 @@ def size(self, f3d): # gsDPLoadMultiTile -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTextureTile_4b(GbiMacro): timg: FImage fmt: str @@ -4978,7 +4959,7 @@ def size(self, f3d): # gsDPLoadMultiTile_4b -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTLUT_pal16(GbiMacro): pal: int dram: FImage # pallete object @@ -5020,7 +5001,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTLUT_pal256(GbiMacro): dram: FImage # pallete object _ptr_amp = True # adds & to name of image @@ -5059,7 +5040,7 @@ def size(self, f3d): return GFX_SIZE * 7 -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadTLUT(GbiMacro): count: int tmemaddr: int @@ -5106,7 +5087,7 @@ def size(self, f3d): # gsDPFillRectangle -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetConvert(GbiMacro): k0: int k1: int @@ -5122,7 +5103,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetKeyR(GbiMacro): cR: int sR: int @@ -5135,7 +5116,7 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPSetKeyGB(GbiMacro): cG: int sG: int @@ -5165,7 +5146,7 @@ def gsDPParam(cmd, param): # gsDPTextureRectangleFlip -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPTextureRectangle(GbiMacro): xl: int yl: int @@ -5196,7 +5177,7 @@ def size(self, f3d): return GFX_SIZE * 2 -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class SPScisTextureRectangle(GbiMacro): xl: int yl: int @@ -5219,25 +5200,25 @@ def size(self, f3d): # gsDPWord -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPFullSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPFULLSYNC) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPTileSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPTILESYNC) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPPipeSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPPIPESYNC) -@dataclass(unsafe_hash = True) +@dataclass(unsafe_hash=True) class DPLoadSync(GbiMacro): def to_binary(self, f3d, segments): return gsDPNoParam(f3d.G_RDPLOADSYNC) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index bb344f58d..59b289bd7 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -364,9 +364,9 @@ def saveMeshWithLargeTexturesByFaces( convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) if fMaterial.imageKey[0] is not None: - fImage0, _ = fModel.getTextureAndHandleShared(fMaterial.imageKey[0]) + fImage0 = fModel.getTextureAndHandleShared(fMaterial.imageKey[0]) if fMaterial.imageKey[1] is not None: - fImage1, _ = fModel.getTextureAndHandleShared(fMaterial.imageKey[1]) + fImage1 = fModel.getTextureAndHandleShared(fMaterial.imageKey[1]) tileLoads = {} faceTileLoads = {} @@ -1512,7 +1512,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex0Tmem, pal0, pal0Len, - imUse0, + im0Use, tex0Flipbook, ) = getAndCheckTexInfo(0, fModel, fMaterial, material, f3dMat.tex0, useDict["Texture 0"]) ( @@ -1526,7 +1526,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Tmem, pal1, pal1Len, - imUse1, + im1Use, tex1Flipbook, ) = getAndCheckTexInfo(1, fModel, fMaterial, material, f3dMat.tex1, useDict["Texture 1"]) @@ -1605,6 +1605,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): loadPal1 = False pal0Addr = 0 pal1Addr = 0 + pal0Use = im0Use + pal1Use = im1Use if isCI: assert useTex0 or useTex1 if not useTex1: @@ -1650,9 +1652,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + str(pal0Len) + " colors, which can't fit in a CI8 palette (256)." ) + # im0Use remains what it was; the CIs in im0 are the same as they + # would be if im0 was alone. But im1 and pal0 depend on both. + im1Use = pal0Use = im0Use + im1Use elif tex0Fmt != tex1Fmt: # One CI8, one CI4 - ci8Pal, ci4Pal = pal0, pal1 if tex0Fmt == "CI8" else pal1, pal0 - ci8PalLen, ci4PalLen = pal0Len, pal1Len if tex0Fmt == "CI8" else pal1Len, pal0Len + ci8Pal, ci4Pal = (pal0, pal1) if tex0Fmt == "CI8" else (pal1, pal0) + ci8PalLen, ci4PalLen = (pal0Len, pal1Len) if tex0Fmt == "CI8" else (pal1Len, pal0Len) if pal0 is None or pal1 is None: if ci8PalLen > 256 - 16: raise PluginError( @@ -1682,6 +1687,13 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + " The CI8 texture must contain up to 240 unique colors," + " plus the same up to 16 colors used in the CI4 texture." ) + # The use for the CI4 texture remains what it was; its CIs are the + # same as if it was alone. But both the palette and the CI8 CIs are affected. + pal0Use = im0Use + im1Use + if tex0Fmt == "CI8": + im0Use = pal0Use + else: + im1Use = pal0Use else: # both CI4 textures if pal0 is None and pal1 is None and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: loadPal0 = True @@ -1698,6 +1710,9 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Share palette 0 pal0 = tempPal pal0Len = tempPalLen + # im0Use remains what it was; the CIs in im0 are the same as they + # would be if im0 was alone. But im1 and pal0 depend on both. + im1Use = pal0Use = im0Use + im1Use else: # Load one palette across 0-1. Put the longer in slot 0 if pal0Len >= pal1Len: @@ -1712,12 +1727,18 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): pal0 = pal1 + pal0 pal0Len = len(pal0) tex0PaletteIndex = 1 + # The up-to-32 entries in pal0 depend on both images. But the + # CIs in both im0 and im1 are the same as if there was no shared palette. + pal0Use = im0Use + im1Use fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] - useSharedCIPalette = isCI and useTex0 and useTex1 and not loadPal1 pal0Name, pal1Name = tex0Name, tex1Name - if useSharedCIPalette: - imUse0 = imUse1 = imUse0 + imUse1 + if isCI and useTex0 and useTex1 and not loadPal1: + if tex0Flipbook is not None or tex1Flipbook is not None: + raise PluginError("TODO: getSharedPaletteName is not correct for flipbooks") pal0Name = getSharedPaletteName(f3dMat) + pal1 = pal0 + writePal0 = loadPal0 and ((not isTex0Ref) or (tex0Flipbook is not None)) + writePal1 = loadPal1 and ((not isTex1Ref) or (tex1Flipbook is not None)) # Assign TMEM addresses sameTextures = ( @@ -1830,33 +1851,19 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Get texture and palette definitions fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: - imageKey0, fImage0, fPalette0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, tex0Name, fMaterial.isTexLarge[0] + imageKey0, fImage0 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex0, im0Use, tex0Name, fMaterial.isTexLarge[0] ) fMaterial.imageKey[0] = imageKey0 if loadPal0: - if fPalette0 is not None: - paletteKey0 = fImage0.paletteKey - else: - paletteKey0, fPalette0 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex0, imUse0, pal0Name, pal0Len - ) - fImage0.paletteKey = paletteKey0 + paletteKey0, fPalette0 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex0, pal0Use, pal0Name, pal0Len) if useTex1: - imageKey1, fImage1, fPalette1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, tex1Name, fMaterial.isTexLarge[1] + imageKey1, fImage1 = saveOrGetTextureDefinition( + fMaterial, fModel, f3dMat.tex1, im1Use, tex1Name, fMaterial.isTexLarge[1] ) fMaterial.imageKey[1] = imageKey1 - if useSharedCIPalette: - fImage1.paletteKey = paletteKey0 if loadPal1: - if fPalette1 is not None: - paletteKey1 = fImage1.paletteKey - else: - paletteKey1, fPalette1 = saveOrGetPaletteDefinition( - fMaterial, fModel, f3dMat.tex1, imUse1, pal1Name, pal1Len - ) - fImage1.paletteKey = paletteKey1 + paletteKey1, fPalette1 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex1, pal1Use, pal1Name, pal1Len) # Write DL entries to load textures and palettes loadGfx = fMaterial.material @@ -1875,12 +1882,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Write texture and palette data, unless exporting textures as PNGs. if convertTextureData: - if loadPal0 and ((not isTex0Ref) or (tex0Flipbook is not None)): + if writePal0: writePaletteData(fPalette0, pal0) if useTex0: if isTex0Ref: if isCI: - fModel.writeTexRefCITextures(tex0Flipbook, paletteKey0, pal0, tex0Fmt, pal0Fmt) + fModel.writeTexRefCITextures(tex0Flipbook, fMaterial, im0Use, pal0, tex0Fmt, pal0Fmt) else: fModel.writeTexRefNonCITextures(tex0Flipbook, tex0Fmt) else: @@ -1888,18 +1895,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): writeCITextureData(f3dMat.tex0.tex, fImage0, pal0, pal0Fmt, tex0Fmt) else: writeNonCITextureData(f3dMat.tex0.tex, fImage0, tex0Fmt) - if loadPal1 and ((not isTex1Ref) or (tex1Flipbook is not None and not useSharedCIPalette)): + if writePal1: writePaletteData(fPalette1, pal1) if useTex1: if isTex1Ref: if isCI: - fModel.writeTexRefCITextures( - tex1Flipbook, - paletteKey0 if useSharedCIPalette else paletteKey1, - pal0 if useSharedCIPalette else pal1, - tex1Fmt, - pal1Fmt, - ) + fModel.writeTexRefCITextures(tex1Flipbook, fMaterial, im1Use, pal1, tex1Fmt, pal1Fmt) else: fModel.writeTexRefNonCITextures(tex1Flipbook, tex1Fmt) else: @@ -2045,13 +2046,13 @@ def saveOrGetPaletteDefinition( paletteKey = FPaletteKey(palFmt, images) # If palette already loaded, return that data. - fPalette, _ = parent.getTextureAndHandleShared(paletteKey) + fPalette = parent.getTextureAndHandleShared(paletteKey) if fPalette is not None: # print(f"Palette already exists") return paletteKey, fPalette if texProp.use_tex_reference: - fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None, False) + fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) return paletteKey, fPalette paletteName = checkDuplicateTextureName(parent, toAlnum(imageName) + "_pal_" + palFmt.lower()) @@ -2064,7 +2065,6 @@ def saveOrGetPaletteDefinition( 1, palLen, paletteFilename, - False, ) parent.addTexture(paletteKey, fPalette, fMaterial) @@ -2087,15 +2087,15 @@ def saveOrGetTextureDefinition( imageKey = getImageKey(texProp, images) # If image already loaded, return that data. - fImage, fPalette = parent.getTextureAndHandleShared(imageKey) + fImage = parent.getTextureAndHandleShared(imageKey) if fImage is not None: # print(f"Image already exists") - return imageKey, fImage, fPalette + return imageKey, fImage if texProp.use_tex_reference: width, height = texProp.tex_reference_size - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - return imageKey, fImage, None + fImage = FImage(texProp.tex_reference, None, None, width, height, None) + return imageKey, fImage name = image.name if image.filepath == "" else image.filepath filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texFmt) + ".inc.c" @@ -2107,12 +2107,11 @@ def saveOrGetTextureDefinition( image.size[0], image.size[1], filename, - False, ) fImage.isLargeTexture = isLarge parent.addTexture(imageKey, fImage, fMaterial) - return imageKey, fImage, None + return imageKey, fImage def getAndCheckTexInfo( diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index ba2d2be80..c6482f673 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -102,9 +102,6 @@ def __init__(self, f3dType, isHWv1, name, DLFormat, drawLayerOverride): self.drawLayerOverride = drawLayerOverride self.flipbooks: list[TextureFlipbook] = [] - # key: first flipbook image - # value: list of flipbook textures in order - self.processedFlipbooks: dict[bpy.types.Image, list[bpy.types.Image]] = {} FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteAll) # Since dynamic textures are handled by scene draw config, flipbooks should only belong to scene model. @@ -146,43 +143,6 @@ def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): ) model.flipbooks.append(flipbook) - def validateCIFlipbook( - self, existingFPalette: FImage, alreadyExists: bool, fPalette: FImage, flipbookImage: bpy.types.Image - ) -> Union[FImage, bool]: - # TODO: Not sure what all the logic is here. Handling repeated textures - # in the same flipbook should be trivial, and repeats of textures between - # different flipbooks should be automatically handled separately because - # of the set of textures used in the image key. - if existingFPalette is None: - if alreadyExists: - if fPalette: - return fPalette - else: - raise PluginError("FPalette not found.") - else: - return False - else: - if ( - alreadyExists # texture already processed for this export - and fPalette is not None # texture is not a repeat within flipbook - and existingFPalette != False # a previous texture used an existing palette - and fPalette != existingFPalette # the palettes do not match - ): - raise PluginError( - f"Cannot reuse a CI texture across multiple flipbooks: {str(flipbookImage)}. " - + f"Flipbook textures should only be reused if they are in the same grouping/order, including LOD skeletons." - ) - elif ( - not alreadyExists # current texture has not been processed yet - and existingFPalette is not None - and existingFPalette != False # a previous texture used an existing palette - ): - raise PluginError( - f"Flipbook textures before this were part of a different palette: {str(flipbookImage)}. " - + f"Flipbook textures should only be reused if they are in the same grouping/order, including LOD skeletons." - ) - return existingFPalette - def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int) -> FImage: # print("Processing flipbook...") model = self.getFlipbookOwner() @@ -194,56 +154,72 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, []) + palName = model.name + "_" + flipbookProp.textures[0].image.name pal = [] - existingFPalette = None - allImages = [flipbookTexture.image for flipbookTexture in flipbookProp.textures] + allImages = [] + for flipbookTexture in flipbookProp.textures: + if flipbookTexture.image not in allImages: + allImages.append(flipbookTexture.image) + for flipbookTexture in flipbookProp.textures: if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") - name = ( + imageName = ( flipbookTexture.name if flipbookProp.exportMode == "Individual" else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) - - imageKey = FImageKey(flipbookTexture.image, texProp.tex_format, texProp.ci_format, allImages) - fImage, fPalette = parent.getTextureAndHandleShared(imageKey) - alreadyExists = fImage is not None - if not alreadyExists: - width, height = texProp.tex_reference_size - fImage = FImage(texProp.tex_reference, None, None, width, height, None, False) - existingFPalette = model.validateCIFlipbook( - existingFPalette, alreadyExists, fPalette, flipbookTexture.image + name = ( + flipbookTexture.image.name if flipbookTexture.image.filepath == "" else flipbookTexture.image.filepath + ) + filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texFmt) + ".inc.c" + + # We don't know yet if this already exists, cause we need the full set + # of images which contribute to the palette, which we don't get until + # writeTexRefCITextures (in case the other texture in multitexture contributes). + # So these get created but may get dropped later. + fImage_temp = FImage( + checkDuplicateTextureName(model, toAlnum(imageName)), + texFormatOf[texProp.tex_format], + texBitSizeOf[texProp.tex_format], + texProp.tex_reference_size[0], + texProp.tex_reference_size[1], + filename, ) pal = mergePalettes(pal, getColorsUsedInImage(flipbookTexture.image, texProp.ci_format)) flipbook.textureNames.append(fImage.name) - flipbook.images.append((flipbookTexture.image, fImage)) + flipbook.images.append((flipbookTexture.image, fImage_temp)) # print(f"Palette length for {palName}: {len(pal)}") # Checked in getAndCheckTexInfo model.addFlipbookWithRepeatCheck(flipbook) - firstImage = flipbookProp.textures[0].image - model.processedFlipbooks[firstImage] = allImages - return allImages, flipbook, pal, palName def writeTexRefCITextures( self, flipbook: Union[TextureFlipbook, None], - paletteKey: FPaletteKey, + fMaterial: FMaterial, + imagesSharingPalette: list[bpy.types.Image], pal: list[int], texFmt: str, palFmt: str, ): if flipbook is None: - return super().writeTexRefCITextures(None, paletteKey, pal, texFmt, palFmt) - assert paletteKey is not None - for image, fImage in flipbook.images: - fImage.paletteKey = paletteKey + return super().writeTexRefCITextures(None, fMaterial, imagesSharingPalette, pal, texFmt, palFmt) + model = self.getFlipbookOwner() + for i in range(len(flipbook.images)): + image, fImage_temp = flipbook.images[i] + imageKey = FImageKey(image, texFmt, palFmt, imagesSharingPalette) + fImage = model.getTextureAndHandleShared(imageKey) + if fImage is not None: + flipbook.images[i] = (image, fImage) + else: + fImage = fImage_temp + model.addTexture(imageKey, fImage, fMaterial) writeCITextureData(image, fImage, pal, texFmt, palFmt) def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int): @@ -261,7 +237,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") - name = ( + imageName = ( flipbookTexture.name if flipbookProp.exportMode == "Individual" else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() @@ -271,7 +247,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M model, texProp, [flipbookTexture.image], - name, + imageName, False, ) flipbook.textureNames.append(fImage.name) From aa69414477731a2090222f7d40069fe7de800eb7 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Thu, 12 Jan 2023 22:39:02 -0800 Subject: [PATCH 15/38] Updated surface sound names --- fast64_internal/oot/oot_collision_classes.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fast64_internal/oot/oot_collision_classes.py b/fast64_internal/oot/oot_collision_classes.py index 3fd0bfaa0..d4318d55e 100644 --- a/fast64_internal/oot/oot_collision_classes.py +++ b/fast64_internal/oot/oot_collision_classes.py @@ -57,20 +57,20 @@ ootEnumCollisionSound = [ ("Custom", "Custom", "Custom"), - ("0x00", "Earth", "Earth"), + ("0x00", "Dirt", "Dirt (aka Earth)"), ("0x01", "Sand", "Sand"), ("0x02", "Stone", "Stone"), - ("0x03", "Wet Stone", "Wet Stone"), + ("0x03", "Jabu", "Jabu-Jabu flesh (aka Wet Stone)"), ("0x04", "Shallow Water", "Shallow Water"), - ("0x05", "Water", "Water"), - ("0x06", "Grass", "Grass"), - ("0x07", "Lava/Goo", "Lava/Goo"), - ("0x08", "Earth", "Earth"), - ("0x09", "Wooden Plank", "Wooden Plank"), - ("0x0A", "Packed Earth/Wood", "Packed Earth/Wood"), - ("0x0B", "Earth", "Earth"), - ("0x0C", "Ceramic/Ice", "Ceramic/Ice"), - ("0x0D", "Loose Earth", "Loose Earth"), + ("0x05", "Deep Water", "Deep Water"), + ("0x06", "Tall Grass", "Tall Grass"), + ("0x07", "Lava", "Lava (aka Goo)"), + ("0x08", "Grass", "Grass (aka Earth 2)"), + ("0x09", "Bridge", "Bridge (aka Wooden Plank)"), + ("0x0A", "Wood", "Wood (aka Packed Earth)"), + ("0x0B", "Soft Dirt", "Soft Dirt (aka Earth 3)"), + ("0x0C", "Ice", "Ice (aka Ceramic)"), + ("0x0D", "Carpet", "Carpet (aka Loose Earth)"), ] ootEnumConveyorSpeed = [ From 6ba397ef8403ebd09d2e1fb5e7a6f3b0fbc600c6 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 15 Jan 2023 22:46:15 -0800 Subject: [PATCH 16/38] Basic large textures working --- fast64_internal/f3d/f3d_writer.py | 273 ++++++++++-------- fast64_internal/sm64/sm64_geolayout_writer.py | 2 +- 2 files changed, 161 insertions(+), 114 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 59b289bd7..42431e92f 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -262,35 +262,52 @@ def findUVBounds(polygon, uv_data): class TileLoad: - def __init__(self, texFormat, tmemWordsAvail, texDimensions): - self.sl = None - self.sh = None - self.tl = None - self.th = None + def __init__(self, texFormat, tmemWordsAvail, texDimensions, materialName, isPointSampled): + self.sl = self.tl = 1000000 # above any actual value + self.sh = self.th = -1 # below any actual value self.texFormat = texFormat + self.is4bit = texBitSizeOf[texFormat] == "G_IM_SIZ_4b" self.tmemWordsAvail = tmemWordsAvail self.texDimensions = texDimensions + self.materialName = materialName + self.isPointSampled = isPointSampled + def getDimensions(self): + assert self.sl <= self.sh and self.tl <= self.th + return [self.sl - self.sh + 1, self.tl - self.th + 1] + def getLow(self, value): - return int(max(math.floor(value), 0)) + if value < 0.0: + raise PluginError( + f"In large texture material {self.materialName}, some UVs are negative, " + + f"make sure all UVs are positive in both dimensions." + ) + value = int(math.floor(value)) + if self.is4bit and (value & 1) != 0: + # Must start on an even texel + value -= 1 + return value def getHigh(self, value, field): - # 1024 wraps around to 0 - # -1 is because the high value is (max value - 1) - # ex. 32 pixel width -> high = 31 - return int(min(math.ceil(value), min(self.texDimensions[field], 1024)) - 1) + #return int(min(math.ceil(value), min(self.texDimensions[field], 1024)) - 1) + value = int(math.ceil(value)) - (1 if self.isPointSampled else 0) + if self.is4bit and (value & 1) == 0: + # Must end on an odd texel + value += 1 + return value - def tryAppend(self, other): - return self.appendTile(other.sl, other.sh, other.tl, other.th) + def expandCoverLoad(self, other): + return self.expandCoverRegion(other.sl, other.sh, other.tl, other.th) - def appendTile(self, sl, sh, tl, th): + def expandCoverRegion(self, sl, sh, tl, th): new_sl = min(sl, self.sl) new_sh = max(sh, self.sh) new_tl = min(tl, self.tl) new_th = max(th, self.th) - newWidth = abs(new_sl - new_sh) + 1 - newHeight = abs(new_tl - new_th) + 1 + assert new_sl <= new_sh and new_tl <= new_th + newWidth = new_sh - new_sl + 1 + newHeight = new_th - new_tl + 1 tmemUsage = getTmemWordUsage(self.texFormat, newWidth, newHeight) @@ -303,31 +320,86 @@ def appendTile(self, sl, sh, tl, th): self.th = new_th return True - def tryAdd(self, points): + def expandCoverPoints(self, points): if len(points) == 0: return True - sl = self.getLow(points[0][0]) - sh = self.getHigh(points[0][0], 0) - tl = self.getLow(points[0][1]) - th = self.getHigh(points[0][1], 1) - - if self.sl is None: - self.sl = sl - self.sh = sh - self.tl = tl - self.th = th - + sl = tl = 1000000 # above any actual value + sh = th = -1 # below any actual value for point in points: sl = min(self.getLow(point[0]), sl) sh = max(self.getHigh(point[0], 0), sh) tl = min(self.getLow(point[1]), tl) th = max(self.getHigh(point[1], 1), th) - return self.appendTile(sl, sh, tl, th) + if not self.expandCoverRegion(sl, sh, tl, th): + raise PluginError( + f"Large texture material {self.materialName}" + + f" has a triangle that needs to cover texels {sl}-{sh} x {tl}-{th}" + + f" ({sh-sl+1} x {th-tl+1} texels) in format {self.texFormat}" + + f", which can't fit in TMEM." + ) + + def wrapToOffset(self): + assert 0 <= self.sl <= self.sh and 0 <= self.tl <= self.th + soffset = (self.sl // self.texDimensions[0]) * self.texDimensions[0] + toffset = (self.tl // self.texDimensions[1]) * self.texDimensions[1] + self.sl -= soffset + self.sh -= soffset + self.tl -= toffset + self.th -= toffset + if self.sh >= 1024 or self.th >= 1024: + raise PluginError( + f"In large texture material {self.materialName}:" + + f" a triangle needs to cover texels {sl}-{sh} x {tl}-{th}" + + f" (image dims are {self.texDimensions}), but image space" + + f" only goes up to 1024 so this cannot be represented." + ) + return (soffset, toffset) - def getDimensions(self): - return [abs(self.sl - self.sh) + 1, abs(self.tl - self.th) + 1] + +def maybeSaveSingleLargeTextureSetup( + i: int, + fMaterial: FMaterial, + fModel: FModel, + fImage: FImage, + gfxOut: GfxList, + texProp: TextureProperty, + tileSettings: TileLoad, + curImgSet: Union[None, int], + curTileLines: list[int] +): + if fMaterial.isTexLarge[i]: + line = getTileLine( + fImage, tileSettings.sl, tileSettings.sh, texBitSizeOf[texProp.tex_format], fModel.f3d) + print(f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th}") + saveTextureLoadOnly( + fImage, + gfxOut, + texProp, + tileSettings, + 7 - i, + fMaterial.largeTexAddr[i], + fModel.f3d, + curImgSet == i, + line == curTileLines[7 - i], + ) + curImgSet = i + curTileLines[7 - i] = line + saveTextureTile( + fImage, + fMaterial, + gfxOut, + texProp, + tileSettings, + i, + fMaterial.largeTexAddr[i], + fMaterial.texPaletteIndex[i], + fModel.f3d, + line == curTileLines[i], + ) + curTileLines[i] = line + return curImgSet def saveMeshWithLargeTexturesByFaces( @@ -363,32 +435,25 @@ def saveMeshWithLargeTexturesByFaces( uv_data = obj.data.uv_layers["UVMap"].data convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) + fImage0 = fImage1 = None if fMaterial.imageKey[0] is not None: fImage0 = fModel.getTextureAndHandleShared(fMaterial.imageKey[0]) if fMaterial.imageKey[1] is not None: fImage1 = fModel.getTextureAndHandleShared(fMaterial.imageKey[1]) tileLoads = {} - faceTileLoads = {} for face in faces: uvs = [UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled) for loopIndex in face.loops] - faceTileLoad = TileLoad(fMaterial.largeTexFmt, fMaterial.largeTexWords, texDimensions) - faceTileLoads[face] = faceTileLoad - if not faceTileLoad.tryAdd(uvs): - raise PluginError( - "Large texture material " - + str(material.name) - + " has a triangle that is too large to fit in a single tile load." - ) + faceTileLoad = TileLoad( + fMaterial.largeTexFmt, fMaterial.largeTexWords, texDimensions, material.name, isPointSampled) + faceTileLoad.expandCoverPoints(uvs) - added = False for tileLoad, sortedFaces in tileLoads.items(): - if tileLoad.tryAppend(faceTileLoad): + if tileLoad.expandCoverLoad(faceTileLoad): sortedFaces.append(face) - added = True break - if not added: + else: tileLoads[faceTileLoad] = [face] tileLoads = list(tileLoads.items()) @@ -399,43 +464,26 @@ def saveMeshWithLargeTexturesByFaces( fMesh.draw.commands.append(SPDisplayList(triGroup.triList)) currentGroupIndex = None + curImgSet = None + curTileLines = [0 for _ in range(8)] for tileLoad, tileFaces in tileLoads: + stOffset = tileLoad.wrapToOffset() revertCommands = GfxList("temp", GfxListTag.Draw, fModel.DLFormat) - triGroup.triList.commands.append(DPPipeSync()) - if fMaterial.isTexLarge[0]: - saveTextureLoadOnly( - fImage0, triGroup.triList, f3dMat.tex0, tileLoad, 7, fMaterial.largeTexAddr[0], fModel.f3d - ) - saveTextureTile( - fImage0, - fMaterial, - triGroup.triList, - f3dMat.tex0, - tileLoad, - 0, - fMaterial.largeTexAddr[0], - fMaterial.texPaletteIndex[0], - fModel.f3d, - ) - if fMaterial.isTexLarge[1]: - saveTextureLoadOnly( - fImage1, triGroup.triList, f3dMat.tex1, tileLoad, 6, fMaterial.largeTexAddr[1], fModel.f3d - ) - saveTextureTile( - fImage1, - fMaterial, - triGroup.triList, - f3dMat.tex1, - tileLoad, - 1, - fMaterial.largeTexAddr[1], - fMaterial.texPaletteIndex[1], - fModel.f3d, - ) + # Need load sync because if some tris are not drawn by the RSP due to being + # off screen, can run directly from one load tile into another with no sync, + # potentially corrupting TMEM + triGroup.triList.commands.append(DPLoadSync()) + curImgSet = maybeSaveSingleLargeTextureSetup( + 0, fMaterial, fModel, fImage0, triGroup.triList, f3dMat.tex0, tileLoad, curImgSet, curTileLines + ) + curImgSet = maybeSaveSingleLargeTextureSetup( + 1, fMaterial, fModel, fImage1, triGroup.triList, f3dMat.tex1, tileLoad, curImgSet, curTileLines + ) triConverter = TriangleConverter( triConverterInfo, texDimensions, + stOffset, material, currentGroupIndex, triGroup.triList, @@ -448,6 +496,8 @@ def saveMeshWithLargeTexturesByFaces( if len(revertCommands.commands) > 0: fMesh.draw.commands.extend(revertCommands.commands) + + firstFace = False triGroup.triList.commands.append(SPEndDisplayList()) @@ -548,6 +598,7 @@ def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod): [0, 0], mathutils.Vector([0, 0, 0, 0]), [32, 32], + None, transformMatrix, False, False, @@ -785,6 +836,7 @@ def saveMeshByFaces( triConverter = TriangleConverter( triConverterInfo, texDimensions, + None, material, currentGroupIndex, triGroup.triList, @@ -928,6 +980,7 @@ def __init__( self, triConverterInfo: TriangleConverterInfo, texDimensions: tuple[int, int], + stOffset: Union[None, tuple[int, int]], material: bpy.types.Material, currentGroupIndex, triList, @@ -955,6 +1008,7 @@ def __init__( uv_data = triConverterInfo.obj.data.uv_layers["UVMap"].data self.convertInfo = LoopConvertInfo(uv_data, triConverterInfo.obj, exportVertexColors) self.texDimensions = texDimensions + self.stOffset = stOffset self.isPointSampled = isPointSampled self.exportVertexColors = exportVertexColors self.tex_scale = material.f3d_mat.tex_scale @@ -1003,6 +1057,7 @@ def processGeometry(self): bufferVert.f3dVert.uv, bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, + self.stOffset, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, @@ -1037,6 +1092,7 @@ def processGeometry(self): bufferVert.f3dVert.uv, bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, + self.stOffset, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, @@ -1226,7 +1282,7 @@ def UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled): uv[1] = 1 - uv[1] loopUV = uv.freeze() - pixelOffset = 0 if isPointSampled else 0.5 + pixelOffset = 0 #if isPointSampled else 0.5 return [ convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset) / 32, convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset) / 32, @@ -1239,6 +1295,7 @@ def convertVertexData( loopUV, loopColorOrNormal, texDimensions, + stOffset, transformMatrix, isPointSampled, exportVertexColors, @@ -1257,6 +1314,7 @@ def convertVertexData( if (isPointSampled or tex_scale[0] == 0 or tex_scale[1] == 0) else (0.5 / tex_scale[0], 0.5 / tex_scale[1]) ) + pixelOffset = stOffset if stOffset is not None else pixelOffset uv = [ convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset[0]), @@ -2155,7 +2213,7 @@ def getAndCheckTexInfo( tmemSize = getTmemWordUsage(texFormat, imageWidth, imageHeight) if imageWidth > 1024 or imageHeight > 1024: - raise PluginError(f'Error in "{material.name}": Any side of an image cannot be greater ' + "than 1024.") + raise PluginError(f'Error in "{material.name}": Image size (even with large textures) limited to 1024 in either dimension.') pal = None palLen = 0 @@ -2204,7 +2262,7 @@ def getAndCheckTexInfo( # Functions for writing texture and palette DLs -def getTileSizeSettings(texProp: TextureProperty, tileSettings, f3d: F3D): +def getTileSizeSettings(texProp: TextureProperty, tileSettings: Union[None, TileLoad], f3d: F3D): if tileSettings is not None: SL = tileSettings.sl TL = tileSettings.tl @@ -2236,42 +2294,36 @@ def saveTextureLoadOnly( fImage: FImage, gfxOut: GfxList, texProp: TextureProperty, - tileSettings, + tileSettings: Union[None, TileLoad], loadtile: int, tmem: int, f3d: F3D, + omitSetTextureImage = False, + omitSetTile = False, ): fmt = texFormatOf[texProp.tex_format] siz = texBitSizeOf[texProp.tex_format] nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) - line = getTileLine(fImage, SL, SH, siz, f3d) # LoadTile will pad rows to 64 bit word alignment, while # LoadBlock assumes this is already done. useLoadBlock = not fImage.isLargeTexture and isPowerOf2(fImage.width) + line = 0 if useLoadBlock else getTileLine(fImage, SL, SH, siz, f3d) + wid = 1 if useLoadBlock else fImage.width if siz == "G_IM_SIZ_4b": if useLoadBlock: dxs = (((fImage.width) * (fImage.height) + 3) >> 2) - 1 dxt = f3d.CALC_DXT_4b(fImage.width) - gfxOut.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_16b", 1, fImage), - DPSetTile(fmt, "G_IM_SIZ_16b", 0, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadBlock(loadtile, 0, 0, dxs, dxt), - ] - ) + siz = "G_IM_SIZ_16b" + loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) else: sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - gfxOut.commands.extend( - [ - DPSetTextureImage(fmt, "G_IM_SIZ_8b", fImage.width >> 1, fImage), - DPSetTile(fmt, "G_IM_SIZ_8b", line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadTile(loadtile, sl2, tl, sh2, th), - ] - ) + siz = "G_IM_SIZ_8b" + wid >>= 1 + loadCommand = DPLoadTile(loadtile, sl2, tl, sh2, th) else: if useLoadBlock: dxs = ( @@ -2279,21 +2331,17 @@ def saveTextureLoadOnly( >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] ) - 1 dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) - gfxOut.commands.extend( - [ - DPSetTextureImage(fmt, siz + "_LOAD_BLOCK", 1, fImage), - DPSetTile(fmt, siz + "_LOAD_BLOCK", 0, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadBlock(loadtile, 0, 0, dxs, dxt), - ] - ) + siz += "_LOAD_BLOCK" + loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) else: - gfxOut.commands.extend( - [ - DPSetTextureImage(fmt, siz, fImage.width, fImage), - DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadTile(loadtile, sl, tl, sh, th), - ] - ) + loadCommand = DPLoadTile(loadtile, sl, tl, sh, th) + + if not omitSetTextureImage: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + if not omitSetTile: + gfxOut.commands.append(DPSetTile( + fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) + gfxOut.commands.append(loadCommand) def saveTextureTile( @@ -2306,6 +2354,7 @@ def saveTextureTile( tmem: int, pal: int, f3d: F3D, + omitSetTile = False, ): if tileSettings is not None: clamp_S = True @@ -2336,13 +2385,11 @@ def saveTextureTile( SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) line = getTileLine(fImage, SL, SH, siz, f3d) + tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) - gfxOut.commands.extend( - [ - DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts), - tileSizeCommand, - ] - ) # added in) + if not omitSetTile: + gfxOut.commands.append(tileCommand) + gfxOut.commands.append(tileSizeCommand) # hasattr check for FTexRect if hasattr(fMaterial, "tileSizeCommands"): diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 4d4f36e94..45fadff99 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -2771,7 +2771,7 @@ def saveSkinnedMeshByMaterial( # fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) # # convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) - # triConverter = TriangleConverter(triConverterInfo, texDimensions, material, + # triConverter = TriangleConverter(triConverterInfo, texDimensions, None, material, # None, triGroup.triList, triGroup.vertexList, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict)) # saveTriangleStrip(triConverter, [skinnedFace.bFace for skinnedFace in skinnedFaceArray], obj.data, True) # saveTriangleStrip(triConverterClass, From b12dd1caff4e4690fb35972ff7d35e42e304aec9 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 16 Jan 2023 22:26:59 -0800 Subject: [PATCH 17/38] In progress TileLoad supporting wrapping --- fast64_internal/f3d/f3d_enums.py | 7 +- fast64_internal/f3d/f3d_material.py | 21 ++- fast64_internal/f3d/f3d_writer.py | 218 ++++++++++++----------- fast64_internal/oot/oot_model_classes.py | 2 +- 4 files changed, 134 insertions(+), 114 deletions(-) diff --git a/fast64_internal/f3d/f3d_enums.py b/fast64_internal/f3d/f3d_enums.py index 918987abd..38aedcc49 100644 --- a/fast64_internal/f3d/f3d_enums.py +++ b/fast64_internal/f3d/f3d_enums.py @@ -335,7 +335,7 @@ ("TEXEL1", "Texture 1", "Texture 1"), ] -texBitSize = { +texBitSizeInt = { "I4": 4, "IA4": 4, "CI4": 4, @@ -364,3 +364,8 @@ ("F3DEX2/LX2", "F3DEX2/LX2", "F3DEX2/LX2"), ("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej"), ] + +enumLargeEdges = [ + ("Clamp", "Clamp", "Clamp outside image bounds"), + ("Wrap", "Wrap", "Wrap outside image bounds (more expensive)"), +] diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index daa76dcfd..4706dec95 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -26,7 +26,7 @@ "G_IM_SIZ_32b": 32, } -texBitSizeOf = { +texBitSizeF3D = { "I4": "G_IM_SIZ_4b", "IA4": "G_IM_SIZ_4b", "CI4": "G_IM_SIZ_4b", @@ -219,7 +219,7 @@ def key(self): def getTmemWordUsage(texFormat, width, height): - texelsPerLine = 64 / bitSizeDict[texBitSizeOf[texFormat]] + texelsPerLine = 64 / texBitSizeInt[texFormat] return math.ceil(width / texelsPerLine) * height @@ -518,6 +518,13 @@ def ui_prop_non_node(self, material, layout, label, name, setName, setProp): prop_input.enabled = setProp return inputGroup + def ui_large(self, material, layout): + layout.prop(material, "use_large_textures") + if material.use_large_textures: + inputGroup = layout.row().split(factor=0.5) + inputGroup.label(text="Large texture edges:") + inputGroup.prop(material, "large_edges", text="") + def ui_scale(self, material, layout): inputGroup = layout.row().split(factor=0.5) prop_input = inputGroup.column() @@ -789,7 +796,7 @@ def draw_simple(self, f3dMat, material, layout, context): inputCol.prop(f3dMat, "uv_basis", text="UV Basis") if useDict["Texture"]: - inputCol.prop(f3dMat, "use_large_textures") + self.ui_large(f3dMat, inputCol) self.ui_scale(f3dMat, inputCol) if useDict["Primitive"] and f3dMat.set_prim: @@ -891,7 +898,7 @@ def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): inputCol.prop(f3dMat, "uv_basis", text="UV Basis") if useDict["Texture"]: - inputCol.prop(f3dMat, "use_large_textures") + self.ui_large(f3dMat, inputCol) self.ui_scale(f3dMat, inputCol) if useDict["Primitive"]: @@ -2267,9 +2274,8 @@ def ui_image( prop_input.label(text="Size: " + str(tex.size[0]) + " x " + str(tex.size[1])) if canUseLargeTextures: - prop_input.label(text="Large texture mode enabled.") - prop_input.label(text="Each triangle must fit in a single tile load.") - prop_input.label(text="UVs must be in the [0, 1024] pixel range.") + prop_input.row().label(text="Large texture mode enabled.") + prop_input.row().label(text="Recommend using Create Large Texture Mesh tool.") else: tmemUsageUI(prop_input, textureProp) @@ -3462,6 +3468,7 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): draw_layer: bpy.props.PointerProperty(type=DrawLayerProperty) use_large_textures: bpy.props.BoolProperty(name="Large Texture Mode") + large_edges: bpy.props.EnumProperty(items=enumLargeEdges, default="Clamp") def key(self) -> F3DMaterialHash: useDefaultLighting = self.set_lights and self.use_default_lighting diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 42431e92f..27e363545 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -12,8 +12,7 @@ getMaterialScrollDimensions, getTmemWordUsage, getTmemMax, - bitSizeDict, - texBitSizeOf, + texBitSizeF3D, texFormatOf, TextureProperty, F3DMaterialProperty, @@ -262,100 +261,121 @@ def findUVBounds(polygon, uv_data): class TileLoad: - def __init__(self, texFormat, tmemWordsAvail, texDimensions, materialName, isPointSampled): + def __init__(self, material, fMaterial, texDimensions): self.sl = self.tl = 1000000 # above any actual value self.sh = self.th = -1 # below any actual value - self.texFormat = texFormat - self.is4bit = texBitSizeOf[texFormat] == "G_IM_SIZ_4b" - self.tmemWordsAvail = tmemWordsAvail + self.texFormat = fMaterial.largeTexFmt + self.is4bit = texBitSizeInt[self.texFormat] == 4 + self.tmemWordsAvail = fMaterial.largeTexWords self.texDimensions = texDimensions - self.materialName = materialName - self.isPointSampled = isPointSampled - - def getDimensions(self): - assert self.sl <= self.sh and self.tl <= self.th - return [self.sl - self.sh + 1, self.tl - self.th + 1] + self.materialName = material.name + self.isPointSampled = isTexturePointSampled(material) + self.largeEdges = material.large_edges + self.faces = [] + self.offsets = [] + def getLow(self, value): - if value < 0.0: - raise PluginError( - f"In large texture material {self.materialName}, some UVs are negative, " - + f"make sure all UVs are positive in both dimensions." - ) value = int(math.floor(value)) + if self.largeEdges == "Clamp": + value = max(value, 0) if self.is4bit and (value & 1) != 0: # Must start on an even texel value -= 1 return value def getHigh(self, value, field): - #return int(min(math.ceil(value), min(self.texDimensions[field], 1024)) - 1) value = int(math.ceil(value)) - (1 if self.isPointSampled else 0) + if self.largeEdges == "Clamp": + value = min(value, self.texDimensions[field] - 1) if self.is4bit and (value & 1) == 0: # Must end on an odd texel value += 1 return value - def expandCoverLoad(self, other): - return self.expandCoverRegion(other.sl, other.sh, other.tl, other.th) - - def expandCoverRegion(self, sl, sh, tl, th): - new_sl = min(sl, self.sl) - new_sh = max(sh, self.sh) - new_tl = min(tl, self.tl) - new_th = max(th, self.th) - assert new_sl <= new_sh and new_tl <= new_th + def fixRegion(self, sl, sh, tl, th): + assert sl <= sh and tl <= th + soffset = int(floor(sl / self.texDimensions[0])) * self.texDimensions[0] + toffset = int(floor(tl / self.texDimensions[1])) * self.texDimensions[1] + sl -= soffset + sh -= soffset + tl -= toffset + th -= toffset + assert sl >= 0 and tl >= 0 + ret = True + if sh >= 1024 or th >= 1024: + ret = False + if sh >= self.texDimensions[0]: + # Load wraps. Load must start a multiple of a TMEM line from the end + # of the texture, in order for the second load (beginning of image) + # to start at a whole line. + texelsPerLine = 64 / texBitSizeInt[self.texFormat] + if texelsPerLine < self.texDimensions[0]: + raise PluginError( + f"In large texture material {self.materialName}:" + + f" large texture must be at least {texelsPerLine} wide." + ) + sl -= self.texDimensions[0] + sl = int(floor(sl / texelsPerLine)) * texelsPerLine + sl += self.texDimensions[0] newWidth = new_sh - new_sl + 1 newHeight = new_th - new_tl + 1 - tmemUsage = getTmemWordUsage(self.texFormat, newWidth, newHeight) - if tmemUsage > self.tmemWordsAvail: - return False - else: - self.sl = new_sl - self.sh = new_sh - self.tl = new_tl - self.th = new_th + ret = False + return ret, sl, sh, tl, th, soffset, toffset + + def initWithFace(self, obj, face): + uv_data = obj.data.uv_layers["UVMap"].data + faceUVs = [UVtoSTLarge(obj, loopIndex, uv_data, self.texDimensions) + for loopIndex in face.loops] + if len(faceUVs) == 0: return True - def expandCoverPoints(self, points): - if len(points) == 0: - return True - - sl = tl = 1000000 # above any actual value - sh = th = -1 # below any actual value - for point in points: - sl = min(self.getLow(point[0]), sl) - sh = max(self.getHigh(point[0], 0), sh) - tl = min(self.getLow(point[1]), tl) - th = max(self.getHigh(point[1], 1), th) - - if not self.expandCoverRegion(sl, sh, tl, th): - raise PluginError( - f"Large texture material {self.materialName}" - + f" has a triangle that needs to cover texels {sl}-{sh} x {tl}-{th}" - + f" ({sh-sl+1} x {th-tl+1} texels) in format {self.texFormat}" - + f", which can't fit in TMEM." - ) - - def wrapToOffset(self): - assert 0 <= self.sl <= self.sh and 0 <= self.tl <= self.th - soffset = (self.sl // self.texDimensions[0]) * self.texDimensions[0] - toffset = (self.tl // self.texDimensions[1]) * self.texDimensions[1] - self.sl -= soffset - self.sh -= soffset - self.tl -= toffset - self.th -= toffset - if self.sh >= 1024 or self.th >= 1024: - raise PluginError( - f"In large texture material {self.materialName}:" - + f" a triangle needs to cover texels {sl}-{sh} x {tl}-{th}" - + f" (image dims are {self.texDimensions}), but image space" - + f" only goes up to 1024 so this cannot be represented." - ) - return (soffset, toffset) + for point in faceUVs: + self.sl = min(self.sl, self.getLow(point[0])) + self.sh = max(self.sh, self.getHigh(point[0], 0)) + self.tl = min(self.tl, self.getLow(point[1])) + self.th = max(self.th, self.getHigh(point[1], 1)) + + ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion( + self.sl, self.sh, self.tl, self.th) + if not ret: + if self.sh >= 1024 or self.th >= 1024: + raise PluginError( + f"Large texture material {self.materialName} has a face that needs" + + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" + + f" (image dims are {self.texDimensions}), but image space" + + f" only goes up to 1024 so this cannot be represented." + ) + else: + raise PluginError( + f"Large texture material {self.materialName} has a face that needs" + + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" + + f" ({self.sh-self.sl+1} x {self.th-self.tl+1} texels) " + + f"in format {self.texFormat}, which can't fit in TMEM." + ) + self.faces.append(face) + self.offsets.append((soffset, toffset)) + + def trySubsume(self, other): + # Could do fancier logic checking across borders, for example if we have + # one loading 60-68 (size 64) and another 0-8, that could be merged to + # one load 60-72. But this is likely to be uncommon and won't be generated + # by the operator. + new_sl = min(self.sl, other.sl) + new_sh = max(self.sh, other.sh) + new_tl = min(self.tl, other.tl) + new_th = max(self.th, other.th) + ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = fixRegion( + new_sl, new_sh, new_tl, new_th) + if not ret: + return False + self.sl, self.sh, self.tl, self.th = new_sl, new_sh, new_tl, new_th + self.faces.extend(other.faces) + self.offsets.extend(other.offsets) + return True def maybeSaveSingleLargeTextureSetup( @@ -371,7 +391,7 @@ def maybeSaveSingleLargeTextureSetup( ): if fMaterial.isTexLarge[i]: line = getTileLine( - fImage, tileSettings.sl, tileSettings.sh, texBitSizeOf[texProp.tex_format], fModel.f3d) + fImage, tileSettings.sl, tileSettings.sh, texBitSizeF3D[texProp.tex_format], fModel.f3d) print(f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th}") saveTextureLoadOnly( fImage, @@ -430,10 +450,6 @@ def saveMeshWithLargeTexturesByFaces( f3dMat = material fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - isPointSampled = isTexturePointSampled(material) - exportVertexColors = isLightingDisabled(material) - uv_data = obj.data.uv_layers["UVMap"].data - convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) fImage0 = fImage1 = None if fMaterial.imageKey[0] is not None: @@ -441,22 +457,16 @@ def saveMeshWithLargeTexturesByFaces( if fMaterial.imageKey[1] is not None: fImage1 = fModel.getTextureAndHandleShared(fMaterial.imageKey[1]) - tileLoads = {} + tileLoads = [] for face in faces: - uvs = [UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled) for loopIndex in face.loops] - - faceTileLoad = TileLoad( - fMaterial.largeTexFmt, fMaterial.largeTexWords, texDimensions, material.name, isPointSampled) - faceTileLoad.expandCoverPoints(uvs) + faceTileLoad = TileLoad(material, fMaterial, texDimensions) + faceTileLoad.initWithFace(obj, face) - for tileLoad, sortedFaces in tileLoads.items(): - if tileLoad.expandCoverLoad(faceTileLoad): - sortedFaces.append(face) + for tileLoad in tileLoads: + if tileLoad.trySubsume(faceTileLoad): break else: - tileLoads[faceTileLoad] = [face] - - tileLoads = list(tileLoads.items()) + tileLoads.append(faceTileLoad) if material.name != lastMaterialName: fMesh.add_material_call(fMaterial) @@ -466,8 +476,7 @@ def saveMeshWithLargeTexturesByFaces( currentGroupIndex = None curImgSet = None curTileLines = [0 for _ in range(8)] - for tileLoad, tileFaces in tileLoads: - stOffset = tileLoad.wrapToOffset() + for tileLoad in tileLoads: revertCommands = GfxList("temp", GfxListTag.Draw, fModel.DLFormat) # Need load sync because if some tris are not drawn by the RSP due to being # off screen, can run directly from one load tile into another with no sync, @@ -483,7 +492,7 @@ def saveMeshWithLargeTexturesByFaces( triConverter = TriangleConverter( triConverterInfo, texDimensions, - stOffset, + tileLoad.offsets, # TODO material, currentGroupIndex, triGroup.triList, @@ -492,7 +501,7 @@ def saveMeshWithLargeTexturesByFaces( copy.deepcopy(matRegionDict), ) - currentGroupIndex = saveTriangleStrip(triConverter, tileFaces, obj.data, False) + currentGroupIndex = saveTriangleStrip(triConverter, tileLoad.faces, obj.data, False) if len(revertCommands.commands) > 0: fMesh.draw.commands.extend(revertCommands.commands) @@ -714,7 +723,6 @@ def getLowestUnvisitedNeighborCountFace(unvisitedFaces, infoDict): def getNextNeighborFace(faces, face, lastEdgeKey, visitedFaces, possibleFaces, infoDict): - if lastEdgeKey is not None: handledEdgeKeys = [lastEdgeKey] nextEdgeKey = face.edge_keys[(face.edge_keys.index(lastEdgeKey) + 1) % 3] @@ -823,10 +831,6 @@ def saveMeshByFaces( print("0 Faces Provided.") return fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - isPointSampled = isTexturePointSampled(material) - exportVertexColors = isLightingDisabled(material) - uv_data = obj.data.uv_layers["UVMap"].data - convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) if material.name != lastMaterialName: fMesh.add_material_call(fMaterial) @@ -1003,13 +1007,12 @@ def __init__( self.triList = triList self.vtxList = vtxList - isPointSampled = isTexturePointSampled(material) exportVertexColors = isLightingDisabled(material) uv_data = triConverterInfo.obj.data.uv_layers["UVMap"].data self.convertInfo = LoopConvertInfo(uv_data, triConverterInfo.obj, exportVertexColors) self.texDimensions = texDimensions self.stOffset = stOffset - self.isPointSampled = isPointSampled + self.isPointSampled = isTexturePointSampled(material) self.exportVertexColors = exportVertexColors self.tex_scale = material.f3d_mat.tex_scale @@ -1277,12 +1280,14 @@ def getHighestFaceWeight(faceWeights): """ -def UVtoST(obj, loopIndex, uv_data, texDimensions, isPointSampled): +def UVtoSTLarge(obj, loopIndex, uv_data, texDimensions): uv = uv_data[loopIndex].uv.copy() uv[1] = 1 - uv[1] loopUV = uv.freeze() - pixelOffset = 0 #if isPointSampled else 0.5 + # Represent the -0.5 texel offset in the UVs themselves in clamping mode + # if desired, rather than here at export + pixelOffset = 0 return [ convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset) / 32, convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset) / 32, @@ -2141,7 +2146,7 @@ def saveOrGetTextureDefinition( image = texProp.tex texFmt = texProp.tex_format texFormat = texFormatOf[texFmt] - bitSize = texBitSizeOf[texFmt] + bitSize = texBitSizeF3D[texFmt] imageKey = getImageKey(texProp, images) # If image already loaded, return that data. @@ -2214,6 +2219,9 @@ def getAndCheckTexInfo( if imageWidth > 1024 or imageHeight > 1024: raise PluginError(f'Error in "{material.name}": Image size (even with large textures) limited to 1024 in either dimension.') + + if texBitSizeInt[texFmt] == 4 and (imageWidth & 1) != 0: + raise PluginError(f'Error in "{material.name}": A 4-bit image must have a width which is even.') pal = None palLen = 0 @@ -2302,7 +2310,7 @@ def saveTextureLoadOnly( omitSetTile = False, ): fmt = texFormatOf[texProp.tex_format] - siz = texBitSizeOf[texProp.tex_format] + siz = texBitSizeF3D[texProp.tex_format] nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) @@ -2381,7 +2389,7 @@ def saveTextureTile( shifts = shift_S if shift_S >= 0 else (shift_S + 16) shiftt = shift_T if shift_T >= 0 else (shift_T + 16) fmt = texFormatOf[texProp.tex_format] - siz = texBitSizeOf[texProp.tex_format] + siz = texBitSizeF3D[texProp.tex_format] SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) line = getTileLine(fImage, SL, SH, siz, f3d) @@ -2531,7 +2539,7 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): if fImage.converted: return fmt = texFormatOf[texFmt] - bitSize = texBitSizeOf[texFmt] + bitSize = texBitSizeF3D[texFmt] pixels = image.pixels[:] if fmt == "G_IM_FMT_RGBA": diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index c6482f673..555827577 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -183,7 +183,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate fImage_temp = FImage( checkDuplicateTextureName(model, toAlnum(imageName)), texFormatOf[texProp.tex_format], - texBitSizeOf[texProp.tex_format], + texBitSizeF3D[texProp.tex_format], texProp.tex_reference_size[0], texProp.tex_reference_size[1], filename, From cc805b52854f69926a9144e916fd9985f77be359 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 17 Jan 2023 22:58:48 -0800 Subject: [PATCH 18/38] Loads across wrap boundaries implemented but not tested --- fast64_internal/f3d/f3d_writer.py | 140 +++++++++++++----- fast64_internal/sm64/sm64_geolayout_writer.py | 7 +- 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 27e363545..9e55ec846 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -271,7 +271,7 @@ def __init__(self, material, fMaterial, texDimensions): self.texDimensions = texDimensions self.materialName = material.name self.isPointSampled = isTexturePointSampled(material) - self.largeEdges = material.large_edges + self.largeEdges = material.f3d_mat.large_edges self.faces = [] self.offsets = [] @@ -296,8 +296,8 @@ def getHigh(self, value, field): def fixRegion(self, sl, sh, tl, th): assert sl <= sh and tl <= th - soffset = int(floor(sl / self.texDimensions[0])) * self.texDimensions[0] - toffset = int(floor(tl / self.texDimensions[1])) * self.texDimensions[1] + soffset = int(math.floor(sl / self.texDimensions[0])) * self.texDimensions[0] + toffset = int(math.floor(tl / self.texDimensions[1])) * self.texDimensions[1] sl -= soffset sh -= soffset tl -= toffset @@ -317,11 +317,9 @@ def fixRegion(self, sl, sh, tl, th): + f" large texture must be at least {texelsPerLine} wide." ) sl -= self.texDimensions[0] - sl = int(floor(sl / texelsPerLine)) * texelsPerLine + sl = int(math.floor(sl / texelsPerLine)) * texelsPerLine sl += self.texDimensions[0] - newWidth = new_sh - new_sl + 1 - newHeight = new_th - new_tl + 1 - tmemUsage = getTmemWordUsage(self.texFormat, newWidth, newHeight) + tmemUsage = getTmemWordUsage(self.texFormat, sh - sl + 1, th - tl + 1) if tmemUsage > self.tmemWordsAvail: ret = False return ret, sl, sh, tl, th, soffset, toffset @@ -368,7 +366,7 @@ def trySubsume(self, other): new_sh = max(self.sh, other.sh) new_tl = min(self.tl, other.tl) new_th = max(self.th, other.th) - ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = fixRegion( + ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = self.fixRegion( new_sl, new_sh, new_tl, new_th) if not ret: return False @@ -385,27 +383,81 @@ def maybeSaveSingleLargeTextureSetup( fImage: FImage, gfxOut: GfxList, texProp: TextureProperty, + texDimensions: tuple[int, int], tileSettings: TileLoad, curImgSet: Union[None, int], curTileLines: list[int] ): if fMaterial.isTexLarge[i]: - line = getTileLine( - fImage, tileSettings.sl, tileSettings.sh, texBitSizeF3D[texProp.tex_format], fModel.f3d) - print(f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th}") - saveTextureLoadOnly( - fImage, - gfxOut, - texProp, - tileSettings, - 7 - i, - fMaterial.largeTexAddr[i], - fModel.f3d, - curImgSet == i, - line == curTileLines[7 - i], - ) + wrapS = tileSettings.sh >= texDimensions[0] + wrapT = tileSettings.th >= texDimensions[1] + assert 0 <= tileSettings.sl < texDimensions[0] + assert 0 <= tileSettings.tl < texDimensions[1] + siz = texBitSizeF3D[texProp.tex_format] + line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) + tmem = fMaterial.largeTexAddr[i] + if wrapS or wrapT: + fmt = texFormatOf[texProp.tex_format] + texelsPerLine = 64 / texBitSizeInt[fmt] + wid = texDimensions[0] + is4bit = siz == "G_IM_SIZ_4b" + if is4bit: + siz = "G_IM_SIZ_8b" + wid >>= 1 + assert (tileSettings.sl & 1) == 0 + assert (tileSettings.sh & 1) == 1 + # TL, TH is always * 4 because tile values are 10.2 fixed. + # SL, SH is * 2 for 4 bit and * 4 otherwise, because actually loading + # 8 bit pairs of texels. Also written using f3d.G_TEXTURE_IMAGE_FRAC. + sm = 2 if is4bit else 4 + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + if curImgSet != i: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): + if line != curTileLines[tidxBase]: + gfxOut.commands.append(DPSetTile( + fmt, siz, line, tmemBase, tidxBase, 0, nocm, 0, 0, nocm, 0, 0)) + curTileLines[tidxBase] = line + if wrapS: + # Break up at the wrap boundary into two tile loads. + # The first load must occupy a whole number of lines. + assert (texDimensions[0] - tileSettings.sl) % texelsPerLine == 0 + sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerLine + gfxOut.commands.append(DPLoadTile( + tidxBase, tileSettings.sl * sm, TL*4, (texDimensions[0] - 1) * sm, TH*4)) + gfxOut.commands.append(DPSetTile( + fmt, siz, line, tmemBase + sLineOfs, tidxBase-1, 0, nocm, 0, 0, nocm, 0, 0)) + curTileLines[tidxBase-1] = -1 + gfxOut.commands.append(DPLoadTile( + tidxBase-1, 0, TL*4, (tileSettings.sh - texDimensions[0]) * sm, TH*4)) + else: + gfxOut.commands.append(DPLoadTile( + tidxBase, tileSettings.sl * sm, TL*4, tileSettings.sh * sm, TH*4)) + if wrapT: + # Break up at the wrap boundary into two loads. No special restrictions. + tLineOfs = line * (texDimensions[1] - tileSettings.tl) + loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) + loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) + else: + loadOneOrTwoS(tmem, 7, tileSettings.tl, tileSettings.th) + if fMaterial.isTexLarge[i^1]: + # May reuse any of the above tiles for the other large texture. + gfxOut.commands.append(DPTileSync()) + else: + print(f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th}") + saveTextureLoadOnly( + fImage, + gfxOut, + texProp, + tileSettings, + 7 - i, + tmem, + fModel.f3d, + curImgSet == i, + line == curTileLines[7 - i], + ) + curTileLines[7 - i] = line curImgSet = i - curTileLines[7 - i] = line saveTextureTile( fImage, fMaterial, @@ -413,7 +465,7 @@ def maybeSaveSingleLargeTextureSetup( texProp, tileSettings, i, - fMaterial.largeTexAddr[i], + tmem, fMaterial.texPaletteIndex[i], fModel.f3d, line == curTileLines[i], @@ -483,16 +535,17 @@ def saveMeshWithLargeTexturesByFaces( # potentially corrupting TMEM triGroup.triList.commands.append(DPLoadSync()) curImgSet = maybeSaveSingleLargeTextureSetup( - 0, fMaterial, fModel, fImage0, triGroup.triList, f3dMat.tex0, tileLoad, curImgSet, curTileLines + 0, fMaterial, fModel, fImage0, triGroup.triList, f3dMat.tex0, + texDimensions, tileLoad, curImgSet, curTileLines ) curImgSet = maybeSaveSingleLargeTextureSetup( - 1, fMaterial, fModel, fImage1, triGroup.triList, f3dMat.tex1, tileLoad, curImgSet, curTileLines + 1, fMaterial, fModel, fImage1, triGroup.triList, f3dMat.tex1, + texDimensions, tileLoad, curImgSet, curTileLines ) triConverter = TriangleConverter( triConverterInfo, texDimensions, - tileLoad.offsets, # TODO material, currentGroupIndex, triGroup.triList, @@ -501,7 +554,13 @@ def saveMeshWithLargeTexturesByFaces( copy.deepcopy(matRegionDict), ) - currentGroupIndex = saveTriangleStrip(triConverter, tileLoad.faces, obj.data, False) + currentGroupIndex = saveTriangleStrip( + triConverter, + tileLoad.faces, + tileLoad.offsets, + obj.data, + False + ) if len(revertCommands.commands) > 0: fMesh.draw.commands.extend(revertCommands.commands) @@ -605,9 +664,9 @@ def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod): obj.data, mathutils.Vector(vertexPos), [0, 0], + None, mathutils.Vector([0, 0, 0, 0]), [32, 32], - None, transformMatrix, False, False, @@ -749,7 +808,7 @@ def getNextNeighborFace(faces, face, lastEdgeKey, visitedFaces, possibleFaces, i return nextFaceAndEdge -def saveTriangleStrip(triConverter, faces, mesh, terminateDL): +def saveTriangleStrip(triConverter, faces, faceSTOffsets, mesh, terminateDL): visitedFaces = [] unvisitedFaces = copy.copy(faces) possibleFaces = [] @@ -770,7 +829,8 @@ def saveTriangleStrip(triConverter, faces, mesh, terminateDL): neighborFace = getLowestUnvisitedNeighborCountFace(unvisitedFaces, infoDict) lastEdgeKey = None - triConverter.addFace(neighborFace) + stOffset = None if faceSTOffsets is None else faceSTOffsets[faces.index(neighborFace)] + triConverter.addFace(neighborFace, stOffset) if neighborFace in visitedFaces: raise PluginError("Repeated face") visitedFaces.append(neighborFace) @@ -840,7 +900,6 @@ def saveMeshByFaces( triConverter = TriangleConverter( triConverterInfo, texDimensions, - None, material, currentGroupIndex, triGroup.triList, @@ -849,7 +908,7 @@ def saveMeshByFaces( copy.deepcopy(matRegionDict), ) - currentGroupIndex = saveTriangleStrip(triConverter, faces, obj.data, True) + currentGroupIndex = saveTriangleStrip(triConverter, faces, None, obj.data, True) if fMaterial.revert is not None: fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) @@ -905,6 +964,7 @@ def __init__( ): self.position: mathutils.Vector = position self.uv: mathutils.Vector = uv + self.stOffset: tuple(int, int) | None = None self.color: mathutils.Vector | None = color self.normal: mathutils.Vector | None = normal @@ -914,6 +974,7 @@ def __eq__(self, other): return ( self.position == other.position and self.uv == other.uv + and self.stOffset == other.stOffset and self.color == other.color and self.normal == other.normal ) @@ -984,7 +1045,6 @@ def __init__( self, triConverterInfo: TriangleConverterInfo, texDimensions: tuple[int, int], - stOffset: Union[None, tuple[int, int]], material: bpy.types.Material, currentGroupIndex, triList, @@ -1011,7 +1071,6 @@ def __init__( uv_data = triConverterInfo.obj.data.uv_layers["UVMap"].data self.convertInfo = LoopConvertInfo(uv_data, triConverterInfo.obj, exportVertexColors) self.texDimensions = texDimensions - self.stOffset = stOffset self.isPointSampled = isTexturePointSampled(material) self.exportVertexColors = exportVertexColors self.tex_scale = material.f3d_mat.tex_scale @@ -1058,9 +1117,9 @@ def processGeometry(self): self.triConverterInfo.mesh, bufferVert.f3dVert.position, bufferVert.f3dVert.uv, + bufferVert.f3dVert.stOffset, bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, - self.stOffset, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, @@ -1093,9 +1152,9 @@ def processGeometry(self): self.triConverterInfo.mesh, bufferVert.f3dVert.position, bufferVert.f3dVert.uv, + bufferVert.f3dVert.stOffset, bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, - self.stOffset, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, self.exportVertexColors, @@ -1110,7 +1169,7 @@ def processGeometry(self): createTriangleCommands(self.vertexBufferTriangles, self.vertBuffer, self.triConverterInfo.f3d.F3DEX_GBI) ) - def addFace(self, face): + def addFace(self, face, stOffset): triIndices = [] addedVerts = [] # verts added to existing vertexBuffer allVerts = [] # all verts not in 'untouched' buffer region @@ -1125,6 +1184,7 @@ def addFace(self, face): bufferVert = BufferVertex( getF3DVert(loop, face, self.convertInfo, self.triConverterInfo.mesh), vertexGroup, face.material_index ) + bufferVert.f3dVert.stOffset = stOffset triIndices.append(bufferVert) if not self.vertInBuffer(bufferVert, face.material_index): addedVerts.append(bufferVert) @@ -1298,9 +1358,9 @@ def convertVertexData( mesh, loopPos, loopUV, + stOffset, loopColorOrNormal, texDimensions, - stOffset, transformMatrix, isPointSampled, exportVertexColors, @@ -2220,7 +2280,7 @@ def getAndCheckTexInfo( if imageWidth > 1024 or imageHeight > 1024: raise PluginError(f'Error in "{material.name}": Image size (even with large textures) limited to 1024 in either dimension.') - if texBitSizeInt[texFmt] == 4 and (imageWidth & 1) != 0: + if texBitSizeInt[texFormat] == 4 and (imageWidth & 1) != 0: raise PluginError(f'Error in "{material.name}": A 4-bit image must have a width which is even.') pal = None diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 45fadff99..3639fafdd 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -2699,6 +2699,7 @@ def saveSkinnedMeshByMaterial( obj.data, bufferVert.f3dVert.position, bufferVert.f3dVert.uv, + bufferVert.f3dVert.stOffset, bufferVert.f3dVert.getColorOrNormal(), texDimensions, parentMatrix, @@ -2771,11 +2772,11 @@ def saveSkinnedMeshByMaterial( # fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) # # convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) - # triConverter = TriangleConverter(triConverterInfo, texDimensions, None, material, + # triConverter = TriangleConverter(triConverterInfo, texDimensions, material, # None, triGroup.triList, triGroup.vertexList, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict)) - # saveTriangleStrip(triConverter, [skinnedFace.bFace for skinnedFace in skinnedFaceArray], obj.data, True) + # saveTriangleStrip(triConverter, [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None, obj.data, True) # saveTriangleStrip(triConverterClass, - # [skinnedFace.bFace for skinnedFace in skinnedFaceArray], + # [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None, # convertInfo, triGroup.triList, triGroup.vertexList, fModel.f3d, # texDimensions, currentMatrix, isPointSampled, exportVertexColors, # copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), From dabffe7bacbecec25b49b935fedb03d439f2e0e3 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Thu, 19 Jan 2023 23:32:06 -0800 Subject: [PATCH 19/38] Seamless tiling of large textures working --- fast64_internal/f3d/f3d_material.py | 2 +- fast64_internal/f3d/f3d_writer.py | 56 ++++++++++++++++++----------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 4706dec95..13ad53894 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -220,7 +220,7 @@ def key(self): def getTmemWordUsage(texFormat, width, height): texelsPerLine = 64 / texBitSizeInt[texFormat] - return math.ceil(width / texelsPerLine) * height + return int(math.ceil(width / texelsPerLine)) * height def getTmemMax(texFormat): diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 9e55ec846..9d17eaeb0 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -276,22 +276,22 @@ def __init__(self, material, fMaterial, texDimensions): self.faces = [] self.offsets = [] - def getLow(self, value): + def getLow(self, value, field): value = int(math.floor(value)) if self.largeEdges == "Clamp": - value = max(value, 0) - if self.is4bit and (value & 1) != 0: - # Must start on an even texel - value -= 1 + value = min(max(value, 0), self.texDimensions[field] - 1) + if self.is4bit: + # Must start on an even texel (round down) + value &= ~1 return value def getHigh(self, value, field): value = int(math.ceil(value)) - (1 if self.isPointSampled else 0) if self.largeEdges == "Clamp": - value = min(value, self.texDimensions[field] - 1) - if self.is4bit and (value & 1) == 0: - # Must end on an odd texel - value += 1 + value = min(max(value, 0), self.texDimensions[field] - 1) + if self.is4bit: + # Must end on an odd texel (round up) + value |= 1 return value def fixRegion(self, sl, sh, tl, th): @@ -302,16 +302,16 @@ def fixRegion(self, sl, sh, tl, th): sh -= soffset tl -= toffset th -= toffset - assert sl >= 0 and tl >= 0 + assert 0 <= sl < self.texDimensions[0] and 0 <= tl < self.texDimensions[1] ret = True if sh >= 1024 or th >= 1024: ret = False if sh >= self.texDimensions[0]: - # Load wraps. Load must start a multiple of a TMEM line from the end - # of the texture, in order for the second load (beginning of image) - # to start at a whole line. - texelsPerLine = 64 / texBitSizeInt[self.texFormat] - if texelsPerLine < self.texDimensions[0]: + # Load wraps in S. Load must start a multiple of a TMEM line from + # the end of the texture, in order for the second load (beginning of + # image) to start at a whole line. + texelsPerLine = 64 // texBitSizeInt[self.texFormat] + if texelsPerLine > self.texDimensions[0]: raise PluginError( f"In large texture material {self.materialName}:" + f" large texture must be at least {texelsPerLine} wide." @@ -319,6 +319,14 @@ def fixRegion(self, sl, sh, tl, th): sl -= self.texDimensions[0] sl = int(math.floor(sl / texelsPerLine)) * texelsPerLine sl += self.texDimensions[0] + if th >= self.texDimensions[1]: + # Load wraps in T. Load must start a multiple of 2 TMEM lines from + # the end of the texture, in order for the second load to have the + # same odd/even line parity as the first (because texels are + # interleaved in TMEM every other line). + tl -= self.texDimensions[1] + tl = int(math.floor(tl / 2.0)) * 2 + tl += self.texDimensions[1] tmemUsage = getTmemWordUsage(self.texFormat, sh - sl + 1, th - tl + 1) if tmemUsage > self.tmemWordsAvail: ret = False @@ -332,9 +340,9 @@ def initWithFace(self, obj, face): return True for point in faceUVs: - self.sl = min(self.sl, self.getLow(point[0])) + self.sl = min(self.sl, self.getLow(point[0], 0)) self.sh = max(self.sh, self.getHigh(point[0], 0)) - self.tl = min(self.tl, self.getLow(point[1])) + self.tl = min(self.tl, self.getLow(point[1], 0)) self.th = max(self.th, self.getHigh(point[1], 1)) ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion( @@ -396,9 +404,12 @@ def maybeSaveSingleLargeTextureSetup( siz = texBitSizeF3D[texProp.tex_format] line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) tmem = fMaterial.largeTexAddr[i] + print( + f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " + + f"tmem {tmem} line {line}") if wrapS or wrapT: fmt = texFormatOf[texProp.tex_format] - texelsPerLine = 64 / texBitSizeInt[fmt] + texelsPerLine = 64 // texBitSizeInt[texProp.tex_format] wid = texDimensions[0] is4bit = siz == "G_IM_SIZ_4b" if is4bit: @@ -423,6 +434,7 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # The first load must occupy a whole number of lines. assert (texDimensions[0] - tileSettings.sl) % texelsPerLine == 0 sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerLine + print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") gfxOut.commands.append(DPLoadTile( tidxBase, tileSettings.sl * sm, TL*4, (texDimensions[0] - 1) * sm, TH*4)) gfxOut.commands.append(DPSetTile( @@ -434,8 +446,11 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): gfxOut.commands.append(DPLoadTile( tidxBase, tileSettings.sl * sm, TL*4, tileSettings.sh * sm, TH*4)) if wrapT: - # Break up at the wrap boundary into two loads. No special restrictions. + # Break up at the wrap boundary into two loads. + # The first load must be even in size (even number of texture rows). + assert (texDimensions[1] - tileSettings.tl) % 2 == 0 tLineOfs = line * (texDimensions[1] - tileSettings.tl) + print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) else: @@ -444,7 +459,6 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # May reuse any of the above tiles for the other large texture. gfxOut.commands.append(DPTileSync()) else: - print(f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th}") saveTextureLoadOnly( fImage, gfxOut, @@ -2349,7 +2363,7 @@ def getTileSizeSettings(texProp: TextureProperty, tileSettings: Union[None, Tile def getTileLine(fImage: FImage, SL: int, SH: int, siz: str, f3d: F3D): - width = int(SH - SL) if fImage.isLargeTexture else int(fImage.width) + width = int(SH - SL + 1) if fImage.isLargeTexture else int(fImage.width) if siz == "G_IM_SIZ_4b": line = (((width + 1) >> 1) + 7) >> 3 else: From 617791ba03bd9cf17d747efa997dabcc411aae07 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sat, 21 Jan 2023 11:07:14 -0800 Subject: [PATCH 20/38] Working on large texture mesh operator --- __init__.py | 2 + fast64_internal/f3d/f3d_writer.py | 169 +++++++++++------------- fast64_internal/f3d/op_largetexture.py | 143 ++++++++++++++++++++ fast64_internal/operators.py | 1 - fast64_internal/sm64/sm64_f3d_writer.py | 4 +- 5 files changed, 226 insertions(+), 93 deletions(-) create mode 100644 fast64_internal/f3d/op_largetexture.py diff --git a/__init__.py b/__init__.py index 48366e36a..bc272254b 100644 --- a/__init__.py +++ b/__init__.py @@ -20,6 +20,7 @@ from .fast64_internal.f3d.f3d_writer import f3d_writer_register, f3d_writer_unregister from .fast64_internal.f3d.f3d_parser import f3d_parser_register, f3d_parser_unregister from .fast64_internal.f3d.flipbook import flipbook_register, flipbook_unregister +from .fast64_internal.f3d.op_largetexture import op_largetexture_register, op_largetexture_unregister, ui_oplargetexture from .fast64_internal.f3d_material_converter import ( MatUpdateConvert, @@ -224,6 +225,7 @@ def draw(self, context): col = self.layout.column() col.operator(ArmatureApplyWithMeshOperator.bl_idname) # col.operator(CreateMetarig.bl_idname) + ui_oplargetexture(col, context) addon_updater_ops.update_notice_box_ui(self, context) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 9d17eaeb0..18b21ddb2 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1638,34 +1638,18 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): useDict = all_combiner_uses(f3dMat) # Get texture info, needed for othermode; also check some texture props - ( - useTex0, - isTex0Ref, - isTex0CI, - tex0Fmt, - pal0Fmt, - tex0Name, - imageDims0, - tex0Tmem, - pal0, - pal0Len, - im0Use, - tex0Flipbook, - ) = getAndCheckTexInfo(0, fModel, fMaterial, material, f3dMat.tex0, useDict["Texture 0"]) - ( - useTex1, - isTex1Ref, - isTex1CI, - tex1Fmt, - pal1Fmt, - tex1Name, - imageDims1, - tex1Tmem, - pal1, - pal1Len, - im1Use, - tex1Flipbook, - ) = getAndCheckTexInfo(1, fModel, fMaterial, material, f3dMat.tex1, useDict["Texture 1"]) + err0, info0 = getTexInfoFromMat(0, f3dMat) + err1, info1 = getTexInfoFromMat(1, f3dMat) + if err0 is not None: + raise PluginError(f"In {material.name} tex0: {err0}") + if err1 is not None: + raise PluginError(f"In {material.name} tex1: {err1}") + (useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt, imageDims0, tex0Tmem) = info0 + (useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt, imageDims1, tex1Tmem) = info1 + tex0Name, pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( + 0, material, fMaterial, fModel, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt) + tex1Name, pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( + 0, material, fMaterial, fModel, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt) isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) @@ -1935,6 +1919,17 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): tex1Addr = tmemSize - tex1Tmem else: # Both textures large + raise PluginError( + 'Error in "' + + material.name + + '": Multitexture with two large textures is not currently supported.' + ) + # Limited cases of 2x large textures could be supported in the + # future. However, these cases are either of questionable + # utility or have substantial restrictions. Most cases could be + # premixed into one texture, or would run out of UV space for + # tiling (1024x1024 in the space of whichever texture had + # smaller pixels), or one of the textures could be non-large. if f3dMat.uv_basis == "TEXEL0": texDimensions = imageDims0 fMaterial.largeTexFmt = tex0Fmt @@ -1950,26 +1945,20 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): elif useTex0: texDimensions = imageDims0 fMaterial.largeTexFmt = tex0Fmt - if tex0Tmem > tmemSize: - fMaterial.isTexLarge[0] = True - fMaterial.largeTexAddr[0] = 0 - fMaterial.largeTexWords = tmemSize - doTex0Load = doTex0Tile = False - tmemOccupied = tmemSize - else: - tmemOccupied = tex0Tmem + fMaterial.isTexLarge[0] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexWords = tmemSize + doTex0Load = doTex0Tile = False + tmemOccupied = tmemSize elif useTex1: tex1Addr = 0 texDimensions = imageDims1 fMaterial.largeTexFmt = tex1Fmt - if tex1Tmem > tmemSize: - fMaterial.isTexLarge[1] = True - fMaterial.largeTexAddr[1] = 0 - fMaterial.largeTexWords = tmemSize - doTex1Load = doTex1Tile = False - tmemOccupied = tmemSize - else: - tmemOccupied = tex1Tmem + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[1] = 0 + fMaterial.largeTexWords = tmemSize + doTex1Load = doTex1Tile = False + tmemOccupied = tmemSize if tmemOccupied > tmemSize: if sameTextures and useLargeTextures: raise PluginError( @@ -2251,52 +2240,70 @@ def saveOrGetTextureDefinition( return imageKey, fImage -def getAndCheckTexInfo( +def getTexInfoFromMat( index: int, - fModel: FModel, - fMaterial: FMaterial, - material: bpy.types.Material, - texProp: TextureProperty, - useDictEntry, + f3dMat: F3DMaterialProperty, ): - if not useDictEntry or not texProp.tex_set: - return False, False, False, "", "", "", (0, 0), 0, None, 0, None, None + texProp = getattr(f3dMat, "tex" + str(index)) + + useDict = all_combiner_uses(f3dMat) + if not useDict["Texture " + str(index)]: + return None, (False, False, False, "", "", (0, 0), 0) + + return getTexInfoFromProp(texProp) + +def getTexInfoFromProp(texProp: TextureProperty): + if not texProp.tex_set: + return None, (False, False, False, "", "", (0, 0), 0) + tex = texProp.tex isTexRef = texProp.use_tex_reference texFormat = texProp.tex_format isCITexture = texFormat[:2] == "CI" palFormat = texProp.ci_format if isCITexture else "" - texName = getTextureName(texProp, fModel.name, None) if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0): - raise PluginError( - "Image " + tex.name + " has either a 0 width or height; image may have been removed from original location." - ) + return f"Image {tex.name} has 0 size; may have been deleted/moved.", None if not isTexRef: if tex is None: - raise PluginError(f"In {material.name}, no texture is selected.") + return f"No texture is selected.", None elif len(tex.pixels) == 0: - raise PluginError( - "Could not load missing texture: " - + tex.name - + ". Make sure this texture has not been deleted or moved on disk." - ) + return f"Image {tex.name} is missing on disk.", None if isTexRef: - imageWidth, imageHeight = texProp.tex_reference_size + width, height = texProp.tex_reference_size else: - imageWidth, imageHeight = tex.size + width, height = tex.size - tmemSize = getTmemWordUsage(texFormat, imageWidth, imageHeight) + tmemSize = getTmemWordUsage(texFormat, width, height) - if imageWidth > 1024 or imageHeight > 1024: - raise PluginError(f'Error in "{material.name}": Image size (even with large textures) limited to 1024 in either dimension.') + if width > 1024 or height > 1024: + return f"Image size (even large textures) limited to 1024 in each dimension.", None - if texBitSizeInt[texFormat] == 4 and (imageWidth & 1) != 0: - raise PluginError(f'Error in "{material.name}": A 4-bit image must have a width which is even.') + if texBitSizeInt[texFormat] == 4 and (width & 1) != 0: + return f"A 4-bit image must have a width which is even.", None + + info = (True, isTexRef, isCITexture, texFormat, palFormat, (width, height), tmemSize) + return None, info + +def getTexInfoAdvanced( + index: int, + material: bpy.types.Material, + fMaterial: FMaterial, + fModel: FModel, + isTexRef: bool, + isCITexture: bool, + texFormat: str, + palFormat: str +): + f3dMat = material.f3dMat + texProp = getattr(f3dMat, "tex" + str(index)) + + texName = getTextureName(texProp, fModel.name, None) + pal = None palLen = 0 if isCITexture: @@ -2313,32 +2320,14 @@ def getAndCheckTexInfo( palLen = len(pal) if palLen > (16 if texFormat == "CI4" else 256): raise PluginError( - "In " - + propName - + ", texture " - + texName + f"Error in {material.name}: {texName}" + (" (all flipbook textures)" if flipbook is not None else "") - + " uses too many unique colors to fit in format" - + texFormat - + "." + + f" uses too many unique colors to fit in format {texFormat}." ) else: imUse, flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) - return ( - True, - isTexRef, - isCITexture, - texFormat, - palFormat, - texName, - (imageWidth, imageHeight), - tmemSize, - pal, - palLen, - imUse, - flipbook, - ) + return texName, pal, palLen, imUse, flipbook # Functions for writing texture and palette DLs diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py new file mode 100644 index 000000000..d1b12183c --- /dev/null +++ b/fast64_internal/f3d/op_largetexture.py @@ -0,0 +1,143 @@ +import bpy, mathutils, math +from bpy.utils import register_class, unregister_class +from .utility import * +from .f3d_writer import getTexInfoFromMat + +def getTexInfoForLarge(material): + f3dMat = material.f3d_mat + if f3dMat is None: + return "This is not a Fast3D material.", None + if not f3dMat.use_large_textures: + return "This is not a large texture material.", None + largeEdges = f3dMat.large_edges + bilinear = f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_BILERP" + err0, info0 = getTexInfoFromMat(0, f3dMat) + err1, info1 = getTexInfoFromMat(1, f3dMat) + if err0 is not None: + return err0, None + if err1 is not None: + return err1, None + (useTex0, isTex0Ref, isTex0CI, tex0Fmt, _, imageDims0, tex0Tmem) = info0 + (useTex1, isTex1Ref, isTex1CI, tex1Fmt, _, imageDims1, tex1Tmem) = info1 + isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) + tmemSize = 256 if isCI else 512 + if not useTex0 and not useTex1: + return "Material does not use textures.", None + if tex0Tmem + tex1Tmem <= tmemSize: + return "Texture(s) fit in TMEM; not large.", None + if useTex0 and useTex1: + if tex0Tmem <= tmemSize // 2: + largeDims = imageDims1 + largeFmt = tex1Fmt + largeWords = tmemSize - tex0Tmem + elif tex1Tmem <= tmemSize // 2: + largeDims = imageDims0 + largeFmt = tex0Fmt + largeWords = tmemSize - tex1Tmem + else: + return "Two large textures not supported.", None + elif useTex0: + largeDims = imageDims0 + largeFmt = tex0Fmt + largeWords = tmemSize + else: + largeDims = imageDims1 + largeFmt = tex1Fmt + largeWords = tmemSize + return None, (largeDims, largeFmt, largeWords, largeEdges, bilinear) + + +class OpLargeTextureProperty(bpy.types.PropertyGroup): + mat: bpy.props.PointerProperty(type=bpy.types.Material) + clamp_border: bpy.props.FloatProperty( + name="Extra border", + description="Amount to extend mesh outwards with clamping from image. Set to 0 for no clamping, " + + "or 0.5 for fast64 classic half-texel offset", + default=0.5, + min=0.0, + ) + total_size_s: bpy.props.IntProperty( + name="Total pixels S", + description="Total number of texels in S after wrapping (e.g. 128 if you want to repeat a 64 size image twice)", + default=256, + min=0, + ) + total_size_t: bpy.props.IntProperty( + name="Total pixels T", + description="Total number of texels in T after wrapping (e.g. 128 if you want to repeat a 64 size image twice)", + default=256, + min=0, + ) + lose_pixels: bpy.props.BoolProperty( + name="Lose pixels (drop thin tris at edges)", + description="Discard thin tris, only a few pixels wide or high, at the edges of the image, " + + "which are needed because bilinear interpolation requires loads to overlap by at least 1 pixel", + default=False, + ) + horizontal: bpy.props.BoolProperty( + name="Horizontal Bias (faster)", + description="Generate more horizontal loads and tris, which requires fewer memory transactions", + default=True, + ) + scale: bpy.props.FloatProperty( + name="Scale (texel size)", + description="Size of each texel in Blender scene units", + default=0.1, + min=0.0, + ) + + +def ui_oplargetexture(layout, context): + layout = layout.box() + prop = context.scene.opLargeTextureProperty + layout.box().label(text="Create Large Texture Mesh:") + prop_split(layout.row(), prop, "mat", "Large tex material:") + if prop.mat is None: + layout.row().label(text="Please select a material.") + return + err, info = getTexInfoForLarge(prop.mat) + if err is not None: + layout.row().label(icon='WARNING', text=err) + return + (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info + layout.row().label(text=f"{largeFmt}, {largeEdges} edges, {'bilinear' if bilinear else 'point sampled'}") + if largeEdges == "Clamp": + layout.row().label(text=f"{largeDims[0]}x{largeDims[1]}") + prop_split(layout.row(), prop, "clamp_border", "Extra border") + else: + prop_split(layout.row(), prop, "total_size_s", f"S: {largeDims[0]} / total:") + prop_split(layout.row(), prop, "total_size_t", f"T: {largeDims[1]} / total:") + if bilinear: + layout.row().prop(prop.lose_pixels) + layout.row().prop(prop.horizontal) + prop_split(layout.row(), prop, "scale", "Scale (texel size)") + layout.row().operator(CreateLargeTextureMesh) + + +class CreateLargeTextureMesh(bpy.types.Operator): + bl_idname = 'object.create_large_texture_mesh' + bl_label = "Create Large Texture Mesh" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + def execute(self, context): + return {"CANCELLED"} + + +op_largetexture_classes = ( + OpLargeTextureProperty, + CreateLargeTextureMesh, +) + + +def op_largetexture_register(): + for cls in op_largetexture_classes: + register_class(cls) + + bpy.types.Scene.opLargeTextureProperty = bpy.props.PointerProperty(type=OpLargeTextureProperty) + + +def op_largetexture_unregister(): + for cls in reversed(op_largetexture_classes): + unregister_class(cls) + + del bpy.types.Scene.opLargeTextureProperty diff --git a/fast64_internal/operators.py b/fast64_internal/operators.py index 366441cdd..bd08d2bc8 100644 --- a/fast64_internal/operators.py +++ b/fast64_internal/operators.py @@ -73,4 +73,3 @@ def store_object_data(self): def cleanup_temp_object_data(self): cleanupTempMeshes() - diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 05fbf5886..e44fdeb8e 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -5,7 +5,7 @@ from bpy.utils import register_class, unregister_class from ..panels import SM64_Panel from ..f3d.f3d_writer import ( - getAndCheckTexInfo, + getTexInfoFromProp, saveOrGetTextureDefinition, saveTextureLoadOnly, saveTextureTile, @@ -364,7 +364,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): drawEndCommands = GfxList("temp", GfxListTag.Draw, DLFormat.Dynamic) - useTex, isTexRef, isTexCI, texFmt, _, texName, imageDims, texTmem, _, _ = getAndCheckTexInfo(0, name, texProp, True) + useTex, isTexRef, isTexCI, texFmt, _, imageDims, texTmem = getTexInfoFromProp(texProp) if not useTex: raise PluginError("In " + name + ": texture disabled.") if isTexCI: From 1081be30c0344f6fc8a645a0c9fae99f8324addc Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sat, 21 Jan 2023 11:27:00 -0800 Subject: [PATCH 21/38] Fixed some formatting --- __init__.py | 2 + fast64_internal/f3d/f3d_writer.py | 122 ++++++++++++++----------- fast64_internal/f3d/op_largetexture.py | 37 ++++---- fast64_internal/operators.py | 106 +++++++++++---------- 4 files changed, 146 insertions(+), 121 deletions(-) diff --git a/__init__.py b/__init__.py index bc272254b..bbea0a574 100644 --- a/__init__.py +++ b/__init__.py @@ -458,6 +458,7 @@ def register(): f3d_writer_register() flipbook_register() f3d_parser_register() + op_largetexture_register() # ROM @@ -484,6 +485,7 @@ def register(): # called on add-on disabling def unregister(): utility_anim_unregister() + op_largetexture_unregister() flipbook_unregister() f3d_writer_unregister() f3d_parser_unregister() diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 18b21ddb2..66d5a461b 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -262,8 +262,8 @@ def findUVBounds(polygon, uv_data): class TileLoad: def __init__(self, material, fMaterial, texDimensions): - self.sl = self.tl = 1000000 # above any actual value - self.sh = self.th = -1 # below any actual value + self.sl = self.tl = 1000000 # above any actual value + self.sh = self.th = -1 # below any actual value self.texFormat = fMaterial.largeTexFmt self.is4bit = texBitSizeInt[self.texFormat] == 4 @@ -272,7 +272,7 @@ def __init__(self, material, fMaterial, texDimensions): self.materialName = material.name self.isPointSampled = isTexturePointSampled(material) self.largeEdges = material.f3d_mat.large_edges - + self.faces = [] self.offsets = [] @@ -334,8 +334,7 @@ def fixRegion(self, sl, sh, tl, th): def initWithFace(self, obj, face): uv_data = obj.data.uv_layers["UVMap"].data - faceUVs = [UVtoSTLarge(obj, loopIndex, uv_data, self.texDimensions) - for loopIndex in face.loops] + faceUVs = [UVtoSTLarge(obj, loopIndex, uv_data, self.texDimensions) for loopIndex in face.loops] if len(faceUVs) == 0: return True @@ -344,9 +343,8 @@ def initWithFace(self, obj, face): self.sh = max(self.sh, self.getHigh(point[0], 0)) self.tl = min(self.tl, self.getLow(point[1], 0)) self.th = max(self.th, self.getHigh(point[1], 1)) - - ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion( - self.sl, self.sh, self.tl, self.th) + + ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion(self.sl, self.sh, self.tl, self.th) if not ret: if self.sh >= 1024 or self.th >= 1024: raise PluginError( @@ -374,8 +372,7 @@ def trySubsume(self, other): new_sh = max(self.sh, other.sh) new_tl = min(self.tl, other.tl) new_th = max(self.th, other.th) - ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = self.fixRegion( - new_sl, new_sh, new_tl, new_th) + ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = self.fixRegion(new_sl, new_sh, new_tl, new_th) if not ret: return False self.sl, self.sh, self.tl, self.th = new_sl, new_sh, new_tl, new_th @@ -394,7 +391,7 @@ def maybeSaveSingleLargeTextureSetup( texDimensions: tuple[int, int], tileSettings: TileLoad, curImgSet: Union[None, int], - curTileLines: list[int] + curTileLines: list[int], ): if fMaterial.isTexLarge[i]: wrapS = tileSettings.sh >= texDimensions[0] @@ -406,7 +403,8 @@ def maybeSaveSingleLargeTextureSetup( tmem = fMaterial.largeTexAddr[i] print( f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " - + f"tmem {tmem} line {line}") + + f"tmem {tmem} line {line}" + ) if wrapS or wrapT: fmt = texFormatOf[texProp.tex_format] texelsPerLine = 64 // texBitSizeInt[texProp.tex_format] @@ -424,10 +422,10 @@ def maybeSaveSingleLargeTextureSetup( nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] if curImgSet != i: gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): if line != curTileLines[tidxBase]: - gfxOut.commands.append(DPSetTile( - fmt, siz, line, tmemBase, tidxBase, 0, nocm, 0, 0, nocm, 0, 0)) + gfxOut.commands.append(DPSetTile(fmt, siz, line, tmemBase, tidxBase, 0, nocm, 0, 0, nocm, 0, 0)) curTileLines[tidxBase] = line if wrapS: # Break up at the wrap boundary into two tile loads. @@ -435,16 +433,21 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): assert (texDimensions[0] - tileSettings.sl) % texelsPerLine == 0 sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerLine print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") - gfxOut.commands.append(DPLoadTile( - tidxBase, tileSettings.sl * sm, TL*4, (texDimensions[0] - 1) * sm, TH*4)) - gfxOut.commands.append(DPSetTile( - fmt, siz, line, tmemBase + sLineOfs, tidxBase-1, 0, nocm, 0, 0, nocm, 0, 0)) - curTileLines[tidxBase-1] = -1 - gfxOut.commands.append(DPLoadTile( - tidxBase-1, 0, TL*4, (tileSettings.sh - texDimensions[0]) * sm, TH*4)) + gfxOut.commands.append( + DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) + ) + gfxOut.commands.append( + DPSetTile(fmt, siz, line, tmemBase + sLineOfs, tidxBase - 1, 0, nocm, 0, 0, nocm, 0, 0) + ) + curTileLines[tidxBase - 1] = -1 + gfxOut.commands.append( + DPLoadTile(tidxBase - 1, 0, TL * 4, (tileSettings.sh - texDimensions[0]) * sm, TH * 4) + ) else: - gfxOut.commands.append(DPLoadTile( - tidxBase, tileSettings.sl * sm, TL*4, tileSettings.sh * sm, TH*4)) + gfxOut.commands.append( + DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, tileSettings.sh * sm, TH * 4) + ) + if wrapT: # Break up at the wrap boundary into two loads. # The first load must be even in size (even number of texture rows). @@ -455,7 +458,7 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) else: loadOneOrTwoS(tmem, 7, tileSettings.tl, tileSettings.th) - if fMaterial.isTexLarge[i^1]: + if fMaterial.isTexLarge[i ^ 1]: # May reuse any of the above tiles for the other large texture. gfxOut.commands.append(DPTileSync()) else: @@ -549,12 +552,28 @@ def saveMeshWithLargeTexturesByFaces( # potentially corrupting TMEM triGroup.triList.commands.append(DPLoadSync()) curImgSet = maybeSaveSingleLargeTextureSetup( - 0, fMaterial, fModel, fImage0, triGroup.triList, f3dMat.tex0, - texDimensions, tileLoad, curImgSet, curTileLines + 0, + fMaterial, + fModel, + fImage0, + triGroup.triList, + f3dMat.tex0, + texDimensions, + tileLoad, + curImgSet, + curTileLines, ) curImgSet = maybeSaveSingleLargeTextureSetup( - 1, fMaterial, fModel, fImage1, triGroup.triList, f3dMat.tex1, - texDimensions, tileLoad, curImgSet, curTileLines + 1, + fMaterial, + fModel, + fImage1, + triGroup.triList, + f3dMat.tex1, + texDimensions, + tileLoad, + curImgSet, + curTileLines, ) triConverter = TriangleConverter( @@ -568,17 +587,11 @@ def saveMeshWithLargeTexturesByFaces( copy.deepcopy(matRegionDict), ) - currentGroupIndex = saveTriangleStrip( - triConverter, - tileLoad.faces, - tileLoad.offsets, - obj.data, - False - ) + currentGroupIndex = saveTriangleStrip(triConverter, tileLoad.faces, tileLoad.offsets, obj.data, False) if len(revertCommands.commands) > 0: fMesh.draw.commands.extend(revertCommands.commands) - + firstFace = False triGroup.triList.commands.append(SPEndDisplayList()) @@ -1647,9 +1660,11 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): (useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt, imageDims0, tex0Tmem) = info0 (useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt, imageDims1, tex1Tmem) = info1 tex0Name, pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( - 0, material, fMaterial, fModel, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt) + 0, material, fMaterial, fModel, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt + ) tex1Name, pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( - 0, material, fMaterial, fModel, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt) + 0, material, fMaterial, fModel, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt + ) isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) @@ -1920,9 +1935,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: # Both textures large raise PluginError( - 'Error in "' - + material.name - + '": Multitexture with two large textures is not currently supported.' + 'Error in "' + material.name + '": Multitexture with two large textures is not currently supported.' ) # Limited cases of 2x large textures could be supported in the # future. However, these cases are either of questionable @@ -2245,18 +2258,18 @@ def getTexInfoFromMat( f3dMat: F3DMaterialProperty, ): texProp = getattr(f3dMat, "tex" + str(index)) - + useDict = all_combiner_uses(f3dMat) if not useDict["Texture " + str(index)]: return None, (False, False, False, "", "", (0, 0), 0) - + return getTexInfoFromProp(texProp) def getTexInfoFromProp(texProp: TextureProperty): if not texProp.tex_set: return None, (False, False, False, "", "", (0, 0), 0) - + tex = texProp.tex isTexRef = texProp.use_tex_reference texFormat = texProp.tex_format @@ -2281,10 +2294,10 @@ def getTexInfoFromProp(texProp: TextureProperty): if width > 1024 or height > 1024: return f"Image size (even large textures) limited to 1024 in each dimension.", None - + if texBitSizeInt[texFormat] == 4 and (width & 1) != 0: return f"A 4-bit image must have a width which is even.", None - + info = (True, isTexRef, isCITexture, texFormat, palFormat, (width, height), tmemSize) return None, info @@ -2297,13 +2310,13 @@ def getTexInfoAdvanced( isTexRef: bool, isCITexture: bool, texFormat: str, - palFormat: str + palFormat: str, ): f3dMat = material.f3dMat texProp = getattr(f3dMat, "tex" + str(index)) - + texName = getTextureName(texProp, fModel.name, None) - + pal = None palLen = 0 if isCITexture: @@ -2369,8 +2382,8 @@ def saveTextureLoadOnly( loadtile: int, tmem: int, f3d: F3D, - omitSetTextureImage = False, - omitSetTile = False, + omitSetTextureImage=False, + omitSetTile=False, ): fmt = texFormatOf[texProp.tex_format] siz = texBitSizeF3D[texProp.tex_format] @@ -2406,12 +2419,11 @@ def saveTextureLoadOnly( loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) else: loadCommand = DPLoadTile(loadtile, sl, tl, sh, th) - + if not omitSetTextureImage: gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) if not omitSetTile: - gfxOut.commands.append(DPSetTile( - fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) + gfxOut.commands.append(DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) gfxOut.commands.append(loadCommand) @@ -2425,7 +2437,7 @@ def saveTextureTile( tmem: int, pal: int, f3d: F3D, - omitSetTile = False, + omitSetTile=False, ): if tileSettings is not None: clamp_S = True diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index d1b12183c..1315aa632 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -1,8 +1,9 @@ import bpy, mathutils, math from bpy.utils import register_class, unregister_class -from .utility import * +from ..utility import * from .f3d_writer import getTexInfoFromMat + def getTexInfoForLarge(material): f3dMat = material.f3d_mat if f3dMat is None: @@ -10,6 +11,8 @@ def getTexInfoForLarge(material): if not f3dMat.use_large_textures: return "This is not a large texture material.", None largeEdges = f3dMat.large_edges + if f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_AVERAGE": + return 'Texture filter "Average" not supported.', None bilinear = f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_BILERP" err0, info0 = getTexInfoFromMat(0, f3dMat) err1, info1 = getTexInfoFromMat(1, f3dMat) @@ -45,14 +48,14 @@ def getTexInfoForLarge(material): largeFmt = tex1Fmt largeWords = tmemSize return None, (largeDims, largeFmt, largeWords, largeEdges, bilinear) - + class OpLargeTextureProperty(bpy.types.PropertyGroup): mat: bpy.props.PointerProperty(type=bpy.types.Material) clamp_border: bpy.props.FloatProperty( name="Extra border", description="Amount to extend mesh outwards with clamping from image. Set to 0 for no clamping, " - + "or 0.5 for fast64 classic half-texel offset", + + "or 0.5 for fast64 classic half-texel offset", default=0.5, min=0.0, ) @@ -71,7 +74,7 @@ class OpLargeTextureProperty(bpy.types.PropertyGroup): lose_pixels: bpy.props.BoolProperty( name="Lose pixels (drop thin tris at edges)", description="Discard thin tris, only a few pixels wide or high, at the edges of the image, " - + "which are needed because bilinear interpolation requires loads to overlap by at least 1 pixel", + + "which are needed because bilinear interpolation requires loads to overlap by at least 1 pixel", default=False, ) horizontal: bpy.props.BoolProperty( @@ -88,7 +91,7 @@ class OpLargeTextureProperty(bpy.types.PropertyGroup): def ui_oplargetexture(layout, context): - layout = layout.box() + layout = layout.box().column() prop = context.scene.opLargeTextureProperty layout.box().label(text="Create Large Texture Mesh:") prop_split(layout.row(), prop, "mat", "Large tex material:") @@ -97,28 +100,30 @@ def ui_oplargetexture(layout, context): return err, info = getTexInfoForLarge(prop.mat) if err is not None: - layout.row().label(icon='WARNING', text=err) + layout.row().label(icon="ERROR", text=err) return (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info - layout.row().label(text=f"{largeFmt}, {largeEdges} edges, {'bilinear' if bilinear else 'point sampled'}") + bilinInfo = "bilinear" if bilinear else "point sampled" + sizeInfo = f", {largeDims[0]}x{largeDims[1]}" if largeEdges == "Clamp" else "" + infoStr = f"{largeFmt}, {largeEdges} edges, {bilinInfo}{sizeInfo}" + layout.row().label(icon="IMAGE", text=infoStr) if largeEdges == "Clamp": - layout.row().label(text=f"{largeDims[0]}x{largeDims[1]}") prop_split(layout.row(), prop, "clamp_border", "Extra border") else: prop_split(layout.row(), prop, "total_size_s", f"S: {largeDims[0]} / total:") prop_split(layout.row(), prop, "total_size_t", f"T: {largeDims[1]} / total:") if bilinear: - layout.row().prop(prop.lose_pixels) - layout.row().prop(prop.horizontal) + layout.row().prop(prop, "lose_pixels") + layout.row().prop(prop, "horizontal") prop_split(layout.row(), prop, "scale", "Scale (texel size)") - layout.row().operator(CreateLargeTextureMesh) - + layout.row().operator("scene.create_large_texture_mesh") + class CreateLargeTextureMesh(bpy.types.Operator): - bl_idname = 'object.create_large_texture_mesh' - bl_label = "Create Large Texture Mesh" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - + bl_idname = "scene.create_large_texture_mesh" + bl_label = "Create Large Texture Mesh" + bl_options = {"REGISTER", "UNDO", "PRESET"} + def execute(self, context): return {"CANCELLED"} diff --git a/fast64_internal/operators.py b/fast64_internal/operators.py index bd08d2bc8..0b7d4c946 100644 --- a/fast64_internal/operators.py +++ b/fast64_internal/operators.py @@ -3,73 +3,79 @@ from .utility import * from .f3d.f3d_material import * + def addMaterialByName(obj, matName, preset): - if matName in bpy.data.materials: - bpy.ops.object.material_slot_add() - obj.material_slots[0].material = bpy.data.materials[matName] - else: - material = createF3DMat(obj, preset = preset) - material.name = matName + if matName in bpy.data.materials: + bpy.ops.object.material_slot_add() + obj.material_slots[0].material = bpy.data.materials[matName] + else: + material = createF3DMat(obj, preset=preset) + material.name = matName + class AddWaterBox(bpy.types.Operator): - # set bl_ properties - bl_idname = 'object.add_water_box' - bl_label = "Add Water Box" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} + # set bl_ properties + bl_idname = "object.add_water_box" + bl_label = "Add Water Box" + bl_options = {"REGISTER", "UNDO", "PRESET"} + + scale: bpy.props.FloatProperty(default=10) + preset: bpy.props.StringProperty(default="Shaded Solid") + matName: bpy.props.StringProperty(default="water_mat") - scale : bpy.props.FloatProperty(default = 10) - preset : bpy.props.StringProperty(default = "Shaded Solid") - matName : bpy.props.StringProperty(default = "water_mat") + def setEmptyType(self, emptyObj): + return None - def setEmptyType(self, emptyObj): - return None + def execute(self, context): + if context.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") - def execute(self, context): - if context.mode != "OBJECT": - bpy.ops.object.mode_set(mode = "OBJECT") + bpy.ops.object.select_all(action="DESELECT") - bpy.ops.object.select_all(action = "DESELECT") + location = mathutils.Vector(bpy.context.scene.cursor.location) + bpy.ops.mesh.primitive_plane_add(size=2 * self.scale, enter_editmode=False, align="WORLD", location=location[:]) + planeObj = context.view_layer.objects.active + planeObj.ignore_collision = True + planeObj.name = "Water Box Mesh" - location = mathutils.Vector(bpy.context.scene.cursor.location) - bpy.ops.mesh.primitive_plane_add(size=2 * self.scale, enter_editmode=False, align='WORLD', location=location[:]) - planeObj = context.view_layer.objects.active - planeObj.ignore_collision = True - planeObj.name = "Water Box Mesh" + addMaterialByName(planeObj, self.matName, self.preset) - addMaterialByName(planeObj, self.matName, self.preset) + location += mathutils.Vector([0, 0, -self.scale]) + bpy.ops.object.empty_add(type="CUBE", radius=self.scale, align="WORLD", location=location[:]) + emptyObj = context.view_layer.objects.active + emptyObj.name = "Water Box" + self.setEmptyType(emptyObj) - location += mathutils.Vector([0,0,-self.scale]) - bpy.ops.object.empty_add(type='CUBE', radius = self.scale, align='WORLD', location=location[:]) - emptyObj = context.view_layer.objects.active - emptyObj.name = "Water Box" - self.setEmptyType(emptyObj) + parentObject(planeObj, emptyObj) - parentObject(planeObj, emptyObj) + return {"FINISHED"} - return {"FINISHED"} class WarningOperator(bpy.types.Operator): - '''Extension of Operator that allows collecting and displaying warnings''' - warnings = set() + """Extension of Operator that allows collecting and displaying warnings""" - def reset_warnings(self): - self.warnings.clear() + warnings = set() - def add_warning(self, warning: str): - self.warnings.add(warning) + def reset_warnings(self): + self.warnings.clear() + + def add_warning(self, warning: str): + self.warnings.add(warning) + + def show_warnings(self): + if len(self.warnings): + self.report({"WARNING"}, "Operator completed with warnings:") + for warning in self.warnings: + self.report({"WARNING"}, warning) + self.reset_warnings() - def show_warnings(self): - if len(self.warnings): - self.report({'WARNING'}, 'Operator completed with warnings:') - for warning in self.warnings: - self.report({'WARNING'}, warning) - self.reset_warnings() class ObjectDataExporter(WarningOperator): - '''Operator that uses warnings and can store original matrixes and meshes for use in exporting''' - def store_object_data(self): - store_original_mtx() - store_original_meshes(self.add_warning) + """Operator that uses warnings and can store original matrixes and meshes for use in exporting""" + + def store_object_data(self): + store_original_mtx() + store_original_meshes(self.add_warning) - def cleanup_temp_object_data(self): - cleanupTempMeshes() + def cleanup_temp_object_data(self): + cleanupTempMeshes() From e5494cfa0b0acd6a572f0d6fa381bb8de5909f77 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sat, 21 Jan 2023 23:30:43 -0800 Subject: [PATCH 22/38] Working on large texture mesh creation --- fast64_internal/f3d/op_largetexture.py | 70 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index 1315aa632..fa125de22 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -1,6 +1,8 @@ -import bpy, mathutils, math +import bpy, mathutils, math, bmesh +from mathutils import Vector from bpy.utils import register_class, unregister_class from ..utility import * +from .f3d_enums import texBitSizeInt from .f3d_writer import getTexInfoFromMat @@ -119,12 +121,78 @@ def ui_oplargetexture(layout, context): layout.row().operator("scene.create_large_texture_mesh") +def createLargeTextureMeshInternal(bm, prop): + # Parameters setup + err, info = getTexInfoForLarge(prop.mat) + if err is not None: + raise PluginError(err) + (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info + texelsPerLine = 64 // texBitSizeInt[largeFmt] + baseTileS, baseTileT = 128, 64 + while True: + if getTmemWordUsage(largeFmt, baseTileS, baseTileT) <= largeWords: + break + baseTileS >>= 1 + if getTmemWordUsage(largeFmt, baseTileS, baseTileT) <= largeWords: + break + baseTileT >>= 1 + if prop.horizontal and baseTileS == baseTileT: + baseTileS <<= 1 + baseTileT >>= 1 + if bilinear: + baseTileS -= 1 + baseTileT -= 1 + # Mesh setup + bm.clear() + uvlayer = bm.loops.layers.uv.new("UVMap") + def addGrid(svals, tvals): + ns, nt = len(svals), len(tvals) + verts = [] + for t in tvals: + for s in svals: + verts.append(bm.verts.new((s * prop.scale, 0.0, -t * prop.scale))) + bm.verts.index_update() + faces = [] + for ti in range(nt-1): + for si in range(ns-1): + faces.append(bm.faces.new(( + verts[ti*ns+(si+1)], + verts[ti*ns+si], + verts[(ti+1)*ns+si], + verts[(ti+1)*ns+(si+1)], + ))) + bm.faces.index_update() + for ti in range(nt-1): + for si in range(ns-1): + f = faces[ti*(ns-1)+si] + f.loops[0][uvlayer].uv = Vector((svals[si+1], tvals[ti])) + f.loops[1][uvlayer].uv = Vector((svals[si], tvals[ti])) + f.loops[2][uvlayer].uv = Vector((svals[si], tvals[ti+1])) + f.loops[3][uvlayer].uv = Vector((svals[si+1], tvals[ti+1])) + TODO() + + class CreateLargeTextureMesh(bpy.types.Operator): bl_idname = "scene.create_large_texture_mesh" bl_label = "Create Large Texture Mesh" bl_options = {"REGISTER", "UNDO", "PRESET"} def execute(self, context): + bpy.ops.object.select_all(action='DESELECT') + prop = context.scene.opLargeTextureProperty + assert prop.mat is not None + mesh = context.blend_data.meshes.new(prop.mat.name + "Mesh") + obj = context.blend_data.objects.new(prop.mat.name + "Mesh", mesh) + mesh.materials.append(prop.mat) + bm = bmesh.new() + bm.from_mesh(mesh) + createLargeTextureMeshInternal(bm, prop) + bm.to_mesh(mesh) + bm.free() + bpy.context.collection.objects.link(obj) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.view3d.view_selected() return {"CANCELLED"} From b7f522efcad384476127b1bf552c86c1fe29f099 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 22 Jan 2023 16:06:31 -0800 Subject: [PATCH 23/38] Large texture mesh generation working --- fast64_internal/f3d/f3d_writer.py | 20 +-- fast64_internal/f3d/op_largetexture.py | 162 ++++++++++++++++++++----- 2 files changed, 145 insertions(+), 37 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 66d5a461b..533c2803d 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -280,7 +280,7 @@ def getLow(self, value, field): value = int(math.floor(value)) if self.largeEdges == "Clamp": value = min(max(value, 0), self.texDimensions[field] - 1) - if self.is4bit: + if self.is4bit and field == 0: # Must start on an even texel (round down) value &= ~1 return value @@ -289,7 +289,7 @@ def getHigh(self, value, field): value = int(math.ceil(value)) - (1 if self.isPointSampled else 0) if self.largeEdges == "Clamp": value = min(max(value, 0), self.texDimensions[field] - 1) - if self.is4bit: + if self.is4bit and field == 0: # Must end on an odd texel (round up) value |= 1 return value @@ -341,7 +341,7 @@ def initWithFace(self, obj, face): for point in faceUVs: self.sl = min(self.sl, self.getLow(point[0], 0)) self.sh = max(self.sh, self.getHigh(point[0], 0)) - self.tl = min(self.tl, self.getLow(point[1], 0)) + self.tl = min(self.tl, self.getLow(point[1], 1)) self.th = max(self.th, self.getHigh(point[1], 1)) ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion(self.sl, self.sh, self.tl, self.th) @@ -1660,10 +1660,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): (useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt, imageDims0, tex0Tmem) = info0 (useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt, imageDims1, tex1Tmem) = info1 tex0Name, pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( - 0, material, fMaterial, fModel, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt + 0, material, fMaterial, fModel, useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt ) tex1Name, pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( - 0, material, fMaterial, fModel, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt + 1, material, fMaterial, fModel, useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt ) isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) @@ -2307,12 +2307,16 @@ def getTexInfoAdvanced( material: bpy.types.Material, fMaterial: FMaterial, fModel: FModel, + useTex: bool, isTexRef: bool, isCITexture: bool, texFormat: str, palFormat: str, ): - f3dMat = material.f3dMat + if not useTex: + return "", None, 0, [], None + + f3dMat = material.f3d_mat texProp = getattr(f3dMat, "tex" + str(index)) texName = getTextureName(texProp, fModel.name, None) @@ -2329,7 +2333,7 @@ def getTexInfoAdvanced( palLen = texProp.pal_reference_size else: assert flipbook is None - pal = getColorsUsedInImage(tex, palFormat) + pal = getColorsUsedInImage(texProp.tex, palFormat) palLen = len(pal) if palLen > (16 if texFormat == "CI4" else 256): raise PluginError( @@ -2567,6 +2571,8 @@ def getColorIndicesOfTexture(image, palette, palFormat): for j in reversed(range(image.size[1])): for i in range(image.size[0]): pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + if pixelColor not in palette: + raise PluginError(f"Bug: {image.name} palette len {len(palette)} missing CI") texture.append(palette.index(pixelColor)) return texture diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index fa125de22..bacb22512 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -3,6 +3,7 @@ from bpy.utils import register_class, unregister_class from ..utility import * from .f3d_enums import texBitSizeInt +from .f3d_material import getTmemWordUsage from .f3d_writer import getTexInfoFromMat @@ -52,10 +53,19 @@ def getTexInfoForLarge(material): return None, (largeDims, largeFmt, largeWords, largeEdges, bilinear) +enumOpLTBias = [ + ("Square", "Square (~1x1)", "Almost square loads, rounded up to nearest line. For meshes which will be deformed (e.g. circular) if results with Weak are not acceptable"), + ("Weak", "Weak (1x1/2x1)", "Square or twice-width loads, depending on format and free memory, e.g. 32x32 or 64x32"), + ("Moderate", "Moderate (4x1/2x1)", "Width 4x or 2x height, e.g. 64x16 or 64x32. Good efficiency balance"), + ("Strong", "Strong (4x1/8x1)", "Width 4x or 8x height, e.g. 64x16 or 128x16. More efficient than Moderate if geometry usually viewed roughly straight-on"), + ("Extreme", "Extreme (ortho+point only)", "Maximum width, up to full texture rows. Maximum efficiency if geometry always aligned to camera (orthographic) and point sampled. Inefficient otherwise"), +] + + class OpLargeTextureProperty(bpy.types.PropertyGroup): mat: bpy.props.PointerProperty(type=bpy.types.Material) clamp_border: bpy.props.FloatProperty( - name="Extra border", + name="Extra clamped border", description="Amount to extend mesh outwards with clamping from image. Set to 0 for no clamping, " + "or 0.5 for fast64 classic half-texel offset", default=0.5, @@ -79,10 +89,11 @@ class OpLargeTextureProperty(bpy.types.PropertyGroup): + "which are needed because bilinear interpolation requires loads to overlap by at least 1 pixel", default=False, ) - horizontal: bpy.props.BoolProperty( - name="Horizontal Bias (faster)", + bias: bpy.props.EnumProperty( + items=enumOpLTBias, + name="Bias (see tooltips)", description="Generate more horizontal loads and tris, which requires fewer memory transactions", - default=True, + default="Moderate", ) scale: bpy.props.FloatProperty( name="Scale (texel size)", @@ -108,15 +119,15 @@ def ui_oplargetexture(layout, context): bilinInfo = "bilinear" if bilinear else "point sampled" sizeInfo = f", {largeDims[0]}x{largeDims[1]}" if largeEdges == "Clamp" else "" infoStr = f"{largeFmt}, {largeEdges} edges, {bilinInfo}{sizeInfo}" - layout.row().label(icon="IMAGE", text=infoStr) + layout.row().label(icon="TEXTURE", text=infoStr) if largeEdges == "Clamp": - prop_split(layout.row(), prop, "clamp_border", "Extra border") + prop_split(layout.row(), prop, "clamp_border", "Extra clamped border") + if bilinear: + layout.row().prop(prop, "lose_pixels") else: prop_split(layout.row(), prop, "total_size_s", f"S: {largeDims[0]} / total:") prop_split(layout.row(), prop, "total_size_t", f"T: {largeDims[1]} / total:") - if bilinear: - layout.row().prop(prop, "lose_pixels") - layout.row().prop(prop, "horizontal") + prop_split(layout.row(), prop, "bias", "Bias (see tooltips)") prop_split(layout.row(), prop, "scale", "Scale (texel size)") layout.row().operator("scene.create_large_texture_mesh") @@ -127,21 +138,50 @@ def createLargeTextureMeshInternal(bm, prop): if err is not None: raise PluginError(err) (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info + is4bit = texBitSizeInt[largeFmt] == 4 texelsPerLine = 64 // texBitSizeInt[largeFmt] - baseTileS, baseTileT = 128, 64 - while True: - if getTmemWordUsage(largeFmt, baseTileS, baseTileT) <= largeWords: - break - baseTileS >>= 1 - if getTmemWordUsage(largeFmt, baseTileS, baseTileT) <= largeWords: - break - baseTileT >>= 1 - if prop.horizontal and baseTileS == baseTileT: - baseTileS <<= 1 - baseTileT >>= 1 + uvScale = [1.0 / largeDims[0], 1.0 / largeDims[1]] + wrapSize = [prop.total_size_s, prop.total_size_t] + # Set up base tile size + if prop.bias == "Square": + maxTexelsInTMEM = largeWords * texelsPerLine + tileSLines = int(math.ceil(math.sqrt(maxTexelsInTMEM) / texelsPerLine)) + elif prop.bias == "Extreme": + targetRows = 4 if bilinear else 2 + # Start with just loading full texture rows, rounded up to lines + tileSLines = int(math.ceil(largeDims[0] / texelsPerLine)) + if largeWords // tileSLines < targetRows: + # If that doesn't give us enough rows, reduce to next power of 2 + d = 1 << int(math.floor(math.log2(largeDims[0]))) + tileSLines = d // texelsPerLine + while largeWords // tileSLines < targetRows: + tileSLines >>= 1 + else: + baseTile = [128, 64] + while True: + if getTmemWordUsage(largeFmt, baseTile[0], baseTile[1]) <= largeWords: + break + baseTile[0] >>= 1 + if getTmemWordUsage(largeFmt, baseTile[0], baseTile[1]) <= largeWords: + break + baseTile[1] >>= 1 + if baseTile[0] == baseTile[1]: + shift = 0 if prop.bias == "Weak" else 1 + else: + assert baseTile[0] == baseTile[1] << 1 + shift = 1 if prop.bias == "Strong" else 0 + baseTile[0] <<= shift + # Even though we have baseTile already, convert back to this format, + # in case available TMEM is not a power of 2 we might get a larger T value. + # (Currently the plugin will always assign a power of 2 size) + tileSLines = baseTile[0] // texelsPerLine + baseTile = [tileSLines * texelsPerLine, largeWords // tileSLines] if bilinear: - baseTileS -= 1 - baseTileT -= 1 + baseTile[0] -= 1 + if is4bit: + baseTile[0] &= ~1 + baseTile[1] -= 1 + print(f"Base tile size: {baseTile[0]}x{baseTile[1]}") # Mesh setup bm.clear() uvlayer = bm.loops.layers.uv.new("UVMap") @@ -165,11 +205,66 @@ def addGrid(svals, tvals): for ti in range(nt-1): for si in range(ns-1): f = faces[ti*(ns-1)+si] - f.loops[0][uvlayer].uv = Vector((svals[si+1], tvals[ti])) - f.loops[1][uvlayer].uv = Vector((svals[si], tvals[ti])) - f.loops[2][uvlayer].uv = Vector((svals[si], tvals[ti+1])) - f.loops[3][uvlayer].uv = Vector((svals[si+1], tvals[ti+1])) - TODO() + def getUV(ds, dt): + return Vector((svals[si+ds] * uvScale[0], 1.0 - tvals[ti+dt] * uvScale[1])) + f.loops[0][uvlayer].uv = getUV(1, 0) + f.loops[1][uvlayer].uv = getUV(0, 0) + f.loops[2][uvlayer].uv = getUV(0, 1) + f.loops[3][uvlayer].uv = getUV(1, 1) + def clampGridDim(dim): + vals = [-prop.clamp_border] + d = baseTile[dim] + imHi = largeDims[dim] - (1 if bilinear else 0) + while d < imHi: + vals.append(d) + d += baseTile[dim] + if not bilinear or not prop.lose_pixels or d == imHi: + vals.append(imHi + prop.clamp_border) + return vals + def wrapGridDim(dim): + # Could create a new grid for wrap tris at the edges, because their loads + # can often be combined due to their smaller sizes. However, this would + # produce a mesh with verts on other edges, and the N64 does not guarantee + # that these meshes won't have holes in them. Prefer correct seamless results + # over saving a few tri draws (the loads will still be combined). + distFromWrap = (texelsPerLine, 2)[dim] + # Number of texels such that if a wrap load could reach the end of the drawn + # region by continuing to load this many texels into the image after wrapping, + # it's worth it to do so (as opposed to only loading row/col 0, and drawing + # the rest with a new tri which shares the load at the beginning of the image). + worthItExtraEnd = max(baseTile[dim] // 8, distFromWrap) if bilinear else 0 + vals = [0] + d = 0 + while True: + assert d <= wrapSize[dim] + if d == wrapSize[dim]: + break + nextWrapBdry = (int(math.floor(d / largeDims[dim])) + 1) * largeDims[dim] + if wrapSize[dim] < nextWrapBdry: + nextWrapBdry = 1000000 + if nextWrapBdry - d <= baseTile[dim]: + # Wrap/edge tile + if (nextWrapBdry - d) % distFromWrap != 0: + raise PluginError("Bug: nextWrapBdry constraint violated") + if wrapSize[dim] - d <= baseTile[dim] and wrapSize[dim] - baseTile[dim] <= worthItExtraEnd: + d = wrapSize[dim] + else: + d = nextWrapBdry + elif wrapSize[dim] - d <= baseTile[dim]: + # Final tile, not at the edge + d = wrapSize[dim] + else: + # Normal tile + d += baseTile[dim] + if nextWrapBdry - d <= baseTile[dim]: + # Round up next wrap/edge tile to its constraint, so round down this + d -= nextWrapBdry + d = int(math.floor(d / distFromWrap)) * distFromWrap + d += nextWrapBdry + vals.append(d) + return vals + func = clampGridDim if largeEdges == "Clamp" else wrapGridDim + addGrid(func(0), func(1)) class CreateLargeTextureMesh(bpy.types.Operator): @@ -181,8 +276,9 @@ def execute(self, context): bpy.ops.object.select_all(action='DESELECT') prop = context.scene.opLargeTextureProperty assert prop.mat is not None - mesh = context.blend_data.meshes.new(prop.mat.name + "Mesh") - obj = context.blend_data.objects.new(prop.mat.name + "Mesh", mesh) + name = prop.mat.name + "Mesh" + mesh = context.blend_data.meshes.new(name) + obj = context.blend_data.objects.new(name, mesh) mesh.materials.append(prop.mat) bm = bmesh.new() bm.from_mesh(mesh) @@ -190,10 +286,16 @@ def execute(self, context): bm.to_mesh(mesh) bm.free() bpy.context.collection.objects.link(obj) + obj.parent_type = 'OBJECT' + for o in context.scene.objects: + if o.name.startswith("Room"): + obj.parent = o + break bpy.context.view_layer.objects.active = obj obj.select_set(True) bpy.ops.view3d.view_selected() - return {"CANCELLED"} + self.report({"INFO"}, f"Created large texture mesh {name}.") + return {"FINISHED"} # must return a set op_largetexture_classes = ( From 933f0f4c5505402e975f865d0f549afd9455857f Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 6 Feb 2023 23:04:07 -0800 Subject: [PATCH 24/38] Some bugfixes with changes to flipbook texture writing --- fast64_internal/f3d/f3d_writer.py | 2 +- fast64_internal/f3d/flipbook.py | 6 +++--- fast64_internal/oot/oot_model_classes.py | 16 +++++++++------- fast64_internal/sm64/sm64_f3d_writer.py | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index ea6d379d8..aebc8d30d 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -2176,7 +2176,7 @@ def saveOrGetTextureDefinition( images: list[bpy.types.Image], imageName: str, isLarge: bool, -) -> tuple[FImageKey, FImage, FImage]: +) -> tuple[FImageKey, FImage]: image = texProp.tex texFmt = texProp.tex_format diff --git a/fast64_internal/f3d/flipbook.py b/fast64_internal/f3d/flipbook.py index 5165ca7f8..2d14d861b 100644 --- a/fast64_internal/f3d/flipbook.py +++ b/fast64_internal/f3d/flipbook.py @@ -19,13 +19,13 @@ class TextureFlipbook: def flipbook_data_to_c(flipbook: TextureFlipbook): newArrayData = "" for textureName in flipbook.textureNames: - newArrayData += textureName + ",\n" + newArrayData += " " + textureName + ",\n" return newArrayData def flipbook_to_c(flipbook: TextureFlipbook, isStatic: bool): newArrayData = "void* " if not isStatic else "static void* " - newArrayData += f"{flipbook.name}[]" + " = { " + newArrayData += f"{flipbook.name}[]" + " = {\n" newArrayData += flipbook_data_to_c(flipbook) newArrayData += "};" return newArrayData @@ -33,7 +33,7 @@ def flipbook_to_c(flipbook: TextureFlipbook, isStatic: bool): def flipbook_2d_to_c(flipbook: TextureFlipbook, isStatic: bool, count: int): newArrayData = "void* " if not isStatic else "static void* " - newArrayData += f"{flipbook.name}[][{len(flipbook.textureNames)}] = {{ " + newArrayData += f"{flipbook.name}[][{len(flipbook.textureNames)}] = {{\n" newArrayData += ("{ " + flipbook_data_to_c(flipbook) + " },\n") * count newArrayData += " };" return newArrayData diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 859541333..3f30e87ea 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -1,8 +1,8 @@ import bpy, os, re, mathutils from typing import Union from ..f3d.f3d_parser import F3DContext, F3DTextureReference, getImportData -from ..f3d.f3d_material import TextureProperty, createF3DMat -from ..utility import PluginError, CData, hexOrDecInt +from ..f3d.f3d_material import TextureProperty, createF3DMat, texFormatOf, texBitSizeF3D +from ..utility import PluginError, CData, hexOrDecInt, getNameFromPath, getTextureSuffixFromFormat, toAlnum from ..f3d.flipbook import TextureFlipbook, FlipbookProperty, usesFlipbook, ootFlipbookReferenceIsValid from ..f3d.f3d_writer import ( @@ -13,10 +13,12 @@ DPSetTile, FImageKey, FPaletteKey, + getColorsUsedInImage, mergePalettes, saveOrGetTextureDefinition, writeCITextureData, writeNonCITextureData, + checkDuplicateTextureName, ) from ..f3d.f3d_gbi import ( @@ -153,7 +155,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate if len(flipbookProp.textures) == 0: raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") - flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, []) + flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) palName = model.name + "_" + flipbookProp.textures[0].image.name pal = [] @@ -174,7 +176,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate name = ( flipbookTexture.image.name if flipbookTexture.image.filepath == "" else flipbookTexture.image.filepath ) - filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texFmt) + ".inc.c" + filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texProp.tex_format) + ".inc.c" # We don't know yet if this already exists, cause we need the full set # of images which contribute to the palette, which we don't get until @@ -191,7 +193,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate pal = mergePalettes(pal, getColorsUsedInImage(flipbookTexture.image, texProp.ci_format)) - flipbook.textureNames.append(fImage.name) + flipbook.textureNames.append(fImage_temp.name) flipbook.images.append((flipbookTexture.image, fImage_temp)) # print(f"Palette length for {palName}: {len(pal)}") # Checked in getAndCheckTexInfo @@ -220,7 +222,7 @@ def writeTexRefCITextures( else: fImage = fImage_temp model.addTexture(imageKey, fImage, fMaterial) - writeCITextureData(image, fImage, pal, texFmt, palFmt) + writeCITextureData(image, fImage, pal, palFmt, texFmt) def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int): model = self.getFlipbookOwner() @@ -242,7 +244,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M if flipbookProp.exportMode == "Individual" else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() ) - _, fImage, _ = saveOrGetTextureDefinition( + _, fImage = saveOrGetTextureDefinition( fMaterial, model, texProp, diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 3ebff078b..2e6f70bb2 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -316,7 +316,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): raise PluginError("In " + name + ": texture is too big (> 4 KiB).") if texFmt != "RGBA16": raise PluginError("In " + name + ": texture format must be RGBA16 (b/c copy mode).") - imageKey, fImage, _ = saveOrGetTextureDefinition(fMaterial, fTexRect, texProp, [tex], texName, False) + imageKey, fImage = saveOrGetTextureDefinition(fMaterial, fTexRect, texProp, [tex], texName, False) saveTextureLoadOnly(fImage, fTexRect.draw, texProp, None, 7, 0, fTexRect.f3d) saveTextureTile(fImage, fMaterial, fTexRect.draw, texProp, None, 0, 0, 0, fTexRect.f3d) if convertTextureData: From d9a87e6c3185589d788485d942d3580dc68d1c95 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 7 Feb 2023 17:33:54 -0800 Subject: [PATCH 25/38] Flipbook export mostly working --- fast64_internal/f3d/f3d_gbi.py | 3 +- fast64_internal/f3d/f3d_material.py | 18 +-- fast64_internal/f3d/f3d_writer.py | 141 ++++++++++++----------- fast64_internal/f3d/flipbook.py | 2 +- fast64_internal/oot/oot_model_classes.py | 59 +++++----- 5 files changed, 114 insertions(+), 109 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 292a072ac..5abfc8047 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2277,11 +2277,10 @@ def processTexRefCITextures(self, fMaterial: "FMaterial", material: bpy.types.Ma image), for creating image / palette keys - an object containing info about the additional textures, or None - the palette to use (or None) - - the palette name (or None) """ texProp = getattr(material.f3d_mat, f"tex{index}") imUse = [] if texProp.tex is None else [texProp.tex] - return imUse, None, None, None + return imUse, None, None def writeTexRefCITextures( self, diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 13ad53894..69d4f671a 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -787,10 +787,10 @@ def draw_simple(self, f3dMat, material, layout, context): self.checkDrawMixedCIWarning(inputCol, useDict, f3dMat) canUseLargeTextures = material.mat_ver > 3 and material.f3d_mat.use_large_textures if useDict["Texture 0"] and f3dMat.tex0.tex_set: - ui_image(canUseLargeTextures, inputCol, f3dMat.tex0, "Texture 0", False) + ui_image(canUseLargeTextures, inputCol, material, f3dMat.tex0, "Texture 0", False) if useDict["Texture 1"] and f3dMat.tex1.tex_set: - ui_image(canUseLargeTextures, inputCol, f3dMat.tex1, "Texture 1", False) + ui_image(canUseLargeTextures, inputCol, material, f3dMat.tex1, "Texture 1", False) if useMultitexture: inputCol.prop(f3dMat, "uv_basis", text="UV Basis") @@ -889,10 +889,10 @@ def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): self.checkDrawMixedCIWarning(inputCol, useDict, f3dMat) canUseLargeTextures = material.mat_ver > 3 and material.f3d_mat.use_large_textures if useDict["Texture 0"]: - ui_image(canUseLargeTextures, inputCol, f3dMat.tex0, "Texture 0", True) + ui_image(canUseLargeTextures, inputCol, material, f3dMat.tex0, "Texture 0", True) if useDict["Texture 1"]: - ui_image(canUseLargeTextures, inputCol, f3dMat.tex1, "Texture 1", True) + ui_image(canUseLargeTextures, inputCol, material, f3dMat.tex1, "Texture 1", True) if useMultitexture: inputCol.prop(f3dMat, "uv_basis", text="UV Basis") @@ -2238,7 +2238,9 @@ def update_combiner_connections_and_preset(self, context: bpy.types.Context): def ui_image( - canUseLargeTextures: bool, layout: bpy.types.UILayout, textureProp: TextureProperty, name: str, showCheckBox: bool + canUseLargeTextures: bool, layout: bpy.types.UILayout, + material: bpy.types.Material, + textureProp: TextureProperty, name: str, showCheckBox: bool ): inputGroup = layout.box().column() @@ -2261,8 +2263,10 @@ def ui_image( prop_split(prop_input, textureProp, "tex_reference", "Texture Reference") prop_split(prop_input, textureProp, "tex_reference_size", "Texture Size") if textureProp.tex_format[:2] == "CI": - prop_split(prop_input, textureProp, "pal_reference", "Palette Reference") - prop_split(prop_input, textureProp, "pal_reference_size", "Palette Size") + flipbook = getattr(material.flipbookGroup, "flipbook" + texIndex) + if flipbook is None or not flipbook.enable: + prop_split(prop_input, textureProp, "pal_reference", "Palette Reference") + prop_split(prop_input, textureProp, "pal_reference_size", "Palette Size") else: prop_input.template_ID( diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index aebc8d30d..84e0081e2 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1618,14 +1618,16 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): raise PluginError(f"In {material.name} tex1: {err1}") (useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt, imageDims0, tex0Tmem) = info0 (useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt, imageDims1, tex1Tmem) = info1 - tex0Name, pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( + pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( 0, material, fMaterial, fModel, useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt ) - tex1Name, pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( + pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( 1, material, fMaterial, fModel, useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt ) isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) + isPal0Ref = isTex0Ref and tex0Flipbook is None + isPal1Ref = isTex1Ref and tex1Flipbook is None if useTex0 and useTex1: if isTex0CI != isTex1CI: @@ -1826,14 +1828,11 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # CIs in both im0 and im1 are the same as if there was no shared palette. pal0Use = im0Use + im1Use fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] - pal0Name, pal1Name = tex0Name, tex1Name + pal0BaseName = getPaletteName(useTex0, isPal0Ref, f3dMat.tex0, tex0Flipbook) + pal1BaseName = getPaletteName(useTex1, isPal1Ref, f3dMat.tex1, tex1Flipbook) if isCI and useTex0 and useTex1 and not loadPal1: - if tex0Flipbook is not None or tex1Flipbook is not None: - raise PluginError("TODO: getSharedPaletteName is not correct for flipbooks") - pal0Name = getSharedPaletteName(f3dMat) + pal0BaseName = pal0BaseName + "_x_" + pal1BaseName pal1 = pal0 - writePal0 = loadPal0 and ((not isTex0Ref) or (tex0Flipbook is not None)) - writePal1 = loadPal1 and ((not isTex1Ref) or (tex1Flipbook is not None)) # Assign TMEM addresses sameTextures = ( @@ -1950,18 +1949,18 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): fImage0 = fImage1 = fPalette0 = fPalette1 = None if useTex0: imageKey0, fImage0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0, im0Use, tex0Name, fMaterial.isTexLarge[0] + fMaterial, fModel, f3dMat.tex0, im0Use, fMaterial.isTexLarge[0] ) fMaterial.imageKey[0] = imageKey0 if loadPal0: - paletteKey0, fPalette0 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex0, pal0Use, pal0Name, pal0Len) + paletteKey0, fPalette0 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex0, isPal0Ref, pal0Use, pal0BaseName, pal0Len) if useTex1: imageKey1, fImage1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1, im1Use, tex1Name, fMaterial.isTexLarge[1] + fMaterial, fModel, f3dMat.tex1, im1Use, fMaterial.isTexLarge[1] ) fMaterial.imageKey[1] = imageKey1 if loadPal1: - paletteKey1, fPalette1 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex1, pal1Use, pal1Name, pal1Len) + paletteKey1, fPalette1 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex1, isPal1Ref, pal1Use, pal1BaseName, pal1Len) # Write DL entries to load textures and palettes loadGfx = fMaterial.material @@ -1980,7 +1979,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Write texture and palette data, unless exporting textures as PNGs. if convertTextureData: - if writePal0: + if loadPal0 and not isPal0Ref: writePaletteData(fPalette0, pal0) if useTex0: if isTex0Ref: @@ -1993,7 +1992,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): writeCITextureData(f3dMat.tex0.tex, fImage0, pal0, pal0Fmt, tex0Fmt) else: writeNonCITextureData(f3dMat.tex0.tex, fImage0, tex0Fmt) - if writePal1: + if loadPal1 and not isPal1Ref: writePaletteData(fPalette1, pal1) if useTex1: if isTex1Ref: @@ -2092,32 +2091,59 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Functions for texture and palette definitions -def getTextureName(texProp: TextureProperty, fModelName: str, overrideName: str) -> str: - tex = texProp.tex - texFormat = texProp.tex_format - if not texProp.use_tex_reference: - if tex.filepath == "": - name = tex.name - else: - name = tex.filepath +def getTextureNamesFromBasename( + baseName: str, + texOrPalFormat: str, + parent: Union[FModel, FTexRect], + isPalette: bool +): + suffix = getTextureSuffixFromFormat(texOrPalFormat) + imageName = parent.name + "_" + baseName + "_" + if isPalette: + imageName += "pal_" + imageName += suffix + imageName = checkDuplicateTextureName(parent, toAlnum(imageName)) + filename = baseName + "." + suffix + (".pal" if isPalette else ".inc.c") + return imageName, filename + + +def getImageName(image: bpy.types.Image): + if image is None: + raise PluginError("No image set in material!") + elif image.filepath == "": + return image.name else: - name = texProp.tex_reference - texName = ( - fModelName - + "_" - + (getNameFromPath(name, True) + "_" + texFormat.lower() if overrideName is None else overrideName) - ) + return getNameFromPath(image.filepath, True) + + +def getTextureNamesFromImage( + image: bpy.types.Image, + texFormat: str, + parent: Union[FModel, FTexRect] +): + return getTextureNamesFromBasename(getImageName(image), texFormat, parent, False) + - return texName +def getTextureNamesFromProp( + texProp: TextureProperty, + parent: Union[FModel, FTexRect] +): + if texProp.use_tex_reference: + raise PluginError("Internal error, invalid use of getTextureNamesFromProp") + return getTextureNamesFromImage(texProp.tex, texProp.tex_format, parent) -def getSharedPaletteName(f3dMat: F3DMaterialProperty): - image0 = f3dMat.tex0.tex - image1 = f3dMat.tex1.tex - texFormat = f3dMat.tex0.tex_format.lower() - tex0Name = getNameFromPath(image0.filepath if image0.filepath != "" else image0.name, True) - tex1Name = getNameFromPath(image1.filepath if image1.filepath != "" else image1.name, True) - return f"{tex0Name}_x_{tex1Name}_{texFormat}" +def getPaletteName( + useTex: bool, + isPalRef: bool, + texProp: TextureProperty, + flipbook: Union[None, "TextureFlipbook"], +): + if not useTex or isPalRef: + return None + if flipbook is not None: + return flipbook.name + return getImageName(texProp.tex) def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): @@ -2133,8 +2159,9 @@ def saveOrGetPaletteDefinition( fMaterial: FMaterial, parent: Union[FModel, FTexRect], texProp: TextureProperty, + isPalRef: bool, images: list[bpy.types.Image], - imageName: str, + palBaseName: str, palLen: int, ) -> tuple[FPaletteKey, FImage]: @@ -2149,21 +2176,12 @@ def saveOrGetPaletteDefinition( # print(f"Palette already exists") return paletteKey, fPalette - if texProp.use_tex_reference: + if isPalRef: fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) return paletteKey, fPalette - paletteName = checkDuplicateTextureName(parent, toAlnum(imageName) + "_pal_" + palFmt.lower()) - paletteFilename = getNameFromPath(imageName, True) + "." + getTextureSuffixFromFormat(texFmt) + ".pal" - - fPalette = FImage( - paletteName, - palFormat, - "G_IM_SIZ_16b", - 1, - palLen, - paletteFilename, - ) + paletteName, filename = getTextureNamesFromBasename(palBaseName, palFmt, parent, True) + fPalette = FImage(paletteName, palFormat, "G_IM_SIZ_16b", 1, palLen, filename) parent.addTexture(paletteKey, fPalette, fMaterial) return paletteKey, fPalette @@ -2174,7 +2192,6 @@ def saveOrGetTextureDefinition( parent: Union[FModel, FTexRect], texProp: TextureProperty, images: list[bpy.types.Image], - imageName: str, isLarge: bool, ) -> tuple[FImageKey, FImage]: @@ -2195,17 +2212,8 @@ def saveOrGetTextureDefinition( fImage = FImage(texProp.tex_reference, None, None, width, height, None) return imageKey, fImage - name = image.name if image.filepath == "" else image.filepath - filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texFmt) + ".inc.c" - - fImage = FImage( - checkDuplicateTextureName(parent, toAlnum(imageName)), - texFormat, - bitSize, - image.size[0], - image.size[1], - filename, - ) + imageName, filename = getTextureNamesFromProp(texProp, parent) + fImage = FImage(imageName, texFormat, bitSize, image.size[0], image.size[1], filename) fImage.isLargeTexture = isLarge parent.addTexture(imageKey, fImage, fMaterial) @@ -2273,21 +2281,18 @@ def getTexInfoAdvanced( palFormat: str, ): if not useTex: - return "", None, 0, [], None + return None, 0, [], None f3dMat = material.f3d_mat texProp = getattr(f3dMat, "tex" + str(index)) - texName = getTextureName(texProp, fModel.name, None) - pal = None palLen = 0 if isCITexture: - imUse, flipbook, pal, palName = fModel.processTexRefCITextures(fMaterial, material, index) + imUse, flipbook, pal = fModel.processTexRefCITextures(fMaterial, material, index) if isTexRef: if flipbook is not None: palLen = len(pal) - texName = palName else: palLen = texProp.pal_reference_size else: @@ -2296,14 +2301,14 @@ def getTexInfoAdvanced( palLen = len(pal) if palLen > (16 if texFormat == "CI4" else 256): raise PluginError( - f"Error in {material.name}: {texName}" + f"Error in {material.name}: texture {index}" + (" (all flipbook textures)" if flipbook is not None else "") + f" uses too many unique colors to fit in format {texFormat}." ) else: imUse, flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) - return texName, pal, palLen, imUse, flipbook + return pal, palLen, imUse, flipbook # Functions for writing texture and palette DLs diff --git a/fast64_internal/f3d/flipbook.py b/fast64_internal/f3d/flipbook.py index 2d14d861b..c11976f73 100644 --- a/fast64_internal/f3d/flipbook.py +++ b/fast64_internal/f3d/flipbook.py @@ -218,7 +218,7 @@ def drawFlipbookGroupProperty( if usesFlipbook(material, flipbook, i, False, checkFlipbookReference): drawFlipbookProperty(layout.column(), flipbook, i) if getattr(material.f3d_mat, "tex" + str(i)).tex_format[:2] == "CI": - layout.label(text="New shared CI palette will be generated.", icon="ERROR") + layout.label(text="New shared CI palette will be generated.", icon="RENDERLAYERS") # START GAME SPECIFIC CALLBACKS diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 3f30e87ea..030fb81c9 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -11,20 +11,19 @@ DPLoadTLUTCmd, DPSetTextureLUT, DPSetTile, - FImageKey, - FPaletteKey, getColorsUsedInImage, mergePalettes, - saveOrGetTextureDefinition, writeCITextureData, writeNonCITextureData, - checkDuplicateTextureName, + getTextureNamesFromImage, ) from ..f3d.f3d_gbi import ( FModel, FMaterial, FImage, + FImageKey, + FPaletteKey, GfxMatWriteMethod, SPDisplayList, GfxList, @@ -157,7 +156,6 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) - palName = model.name + "_" + flipbookProp.textures[0].image.name pal = [] allImages = [] for flipbookTexture in flipbookProp.textures: @@ -168,22 +166,16 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") - imageName = ( - flipbookTexture.name - if flipbookProp.exportMode == "Individual" - else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() - ) - name = ( - flipbookTexture.image.name if flipbookTexture.image.filepath == "" else flipbookTexture.image.filepath - ) - filename = getNameFromPath(name, True) + "." + getTextureSuffixFromFormat(texProp.tex_format) + ".inc.c" - + imageName, filename = getTextureNamesFromImage(flipbookTexture.image, texProp.tex_format, model) + if flipbookProp.exportMode == "Individual": + imageName = flipbookTexture.name + # We don't know yet if this already exists, cause we need the full set # of images which contribute to the palette, which we don't get until # writeTexRefCITextures (in case the other texture in multitexture contributes). # So these get created but may get dropped later. fImage_temp = FImage( - checkDuplicateTextureName(model, toAlnum(imageName)), + imageName, texFormatOf[texProp.tex_format], texBitSizeF3D[texProp.tex_format], texProp.tex_reference_size[0], @@ -196,10 +188,10 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbook.textureNames.append(fImage_temp.name) flipbook.images.append((flipbookTexture.image, fImage_temp)) - # print(f"Palette length for {palName}: {len(pal)}") # Checked in getAndCheckTexInfo + # print(f"Palette length: {len(pal)}") # Checked in getAndCheckTexInfo model.addFlipbookWithRepeatCheck(flipbook) - return allImages, flipbook, pal, palName + return allImages, flipbook, pal def writeTexRefCITextures( self, @@ -239,19 +231,24 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M if flipbookTexture.image is None: raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") - imageName = ( - flipbookTexture.name - if flipbookProp.exportMode == "Individual" - else model.name + "_" + flipbookTexture.image.name + "_" + texProp.tex_format.lower() - ) - _, fImage = saveOrGetTextureDefinition( - fMaterial, - model, - texProp, - [flipbookTexture.image], - imageName, - False, - ) + # Can't use saveOrGetTextureDefinition because the way it gets the + # image key and the name from the texture property won't work here. + imageKey = FImageKey(flipbookTexture.image, texProp.tex_format, texProp.ci_format, [flipbookTexture.image]) + fImage = model.getTextureAndHandleShared(imageKey) + if fImage is None: + imageName, filename = getTextureNamesFromImage(flipbookTexture.image, texProp.tex_format, model) + if flipbookProp.exportMode == "Individual": + imageName = flipbookTexture.name + fImage = FImage( + imageName, + texFormatOf[texProp.tex_format], + texBitSizeF3D[texProp.tex_format], + flipbookTexture.image.size[0], + flipbookTexture.image.size[1], + filename, + ) + model.addTexture(imageKey, fImage, fMaterial) + flipbook.textureNames.append(fImage.name) flipbook.images.append((flipbookTexture.image, fImage)) From f2ee0a56b7ff6401b1116cb8cb92a9a96e193586 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 7 Feb 2023 18:33:43 -0800 Subject: [PATCH 26/38] Some flipbook fixes --- fast64_internal/oot/oot_model_classes.py | 32 +++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 030fb81c9..225689e42 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -144,6 +144,22 @@ def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): ) model.flipbooks.append(flipbook) + def validateImages(self, material: bpy.types.Material, index: int): + flipbookProp = getattr(material.flipbookGroup, f"flipbook{index}") + texProp = getattr(material.f3d_mat, f"tex{index}") + allImages = [] + refSize = (texProp.tex_reference_size[0], texProp.tex_reference_size[1]) + for flipbookTexture in flipbookProp.textures: + if flipbookTexture.image is None: + raise PluginError(f"Flipbook for {material.name} has a texture array item that has not been set.") + imSize = (flipbookTexture.image.size[0], flipbookTexture.image.size[1]) + if imSize != refSize: + raise PluginError(f"In {material.name}: texture reference size is {refSize}, " + + f"but flipbook image {flipbookTexture.image.filepath} size is {imSize}.") + if flipbookTexture.image not in allImages: + allImages.append(flipbookTexture.image) + return allImages + def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int) -> FImage: # print("Processing flipbook...") model = self.getFlipbookOwner() @@ -157,14 +173,8 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) pal = [] - allImages = [] + allImages = self.validateImages(material, index) for flipbookTexture in flipbookProp.textures: - if flipbookTexture.image not in allImages: - allImages.append(flipbookTexture.image) - - for flipbookTexture in flipbookProp.textures: - if flipbookTexture.image is None: - raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") imageName, filename = getTextureNamesFromImage(flipbookTexture.image, texProp.tex_format, model) if flipbookProp.exportMode == "Individual": @@ -178,8 +188,8 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate imageName, texFormatOf[texProp.tex_format], texBitSizeF3D[texProp.tex_format], - texProp.tex_reference_size[0], - texProp.tex_reference_size[1], + flipbookTexture.image.size[0], + flipbookTexture.image.size[1], filename, ) @@ -226,10 +236,8 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M raise PluginError(f"{str(material)} cannot have a flipbook material with no flipbook textures.") flipbook = TextureFlipbook(flipbookProp.name, flipbookProp.exportMode, [], []) - allImages = [flipbookTexture.image for flipbookTexture in flipbookProp.textures] + allImages = self.validateImages(material, index) for flipbookTexture in flipbookProp.textures: - if flipbookTexture.image is None: - raise PluginError(f"Flipbook for {fMaterial.name} has a texture array item that has not been set.") # print(f"Texture: {str(flipbookTexture.image)}") # Can't use saveOrGetTextureDefinition because the way it gets the # image key and the name from the texture property won't work here. From bb67fe3ae100103145f4fdd01a1d7e0a7cfcb7f2 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Fri, 10 Feb 2023 22:59:27 -0800 Subject: [PATCH 27/38] Fixed bug with reused textures between multiple flipbooks --- fast64_internal/f3d/f3d_writer.py | 30 ++++++++++++------------ fast64_internal/oot/oot_model_classes.py | 24 +++++++++++-------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 84e0081e2..aae3f6731 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -360,10 +360,10 @@ def maybeSaveSingleLargeTextureSetup( siz = texBitSizeF3D[texProp.tex_format] line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) tmem = fMaterial.largeTexAddr[i] - print( - f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " - + f"tmem {tmem} line {line}" - ) + # print( + # f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " + # + f"tmem {tmem} line {line}" + # ) if wrapS or wrapT: fmt = texFormatOf[texProp.tex_format] texelsPerLine = 64 // texBitSizeInt[texProp.tex_format] @@ -391,7 +391,7 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # The first load must occupy a whole number of lines. assert (texDimensions[0] - tileSettings.sl) % texelsPerLine == 0 sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerLine - print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") + # print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") gfxOut.commands.append( DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) ) @@ -412,7 +412,7 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # The first load must be even in size (even number of texture rows). assert (texDimensions[1] - tileSettings.tl) % 2 == 0 tLineOfs = line * (texDimensions[1] - tileSettings.tl) - print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") + # print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) else: @@ -2170,16 +2170,16 @@ def saveOrGetPaletteDefinition( palFormat = texFormatOf[palFmt] paletteKey = FPaletteKey(palFmt, images) + if isPalRef: + fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) + return paletteKey, fPalette + # If palette already loaded, return that data. fPalette = parent.getTextureAndHandleShared(paletteKey) if fPalette is not None: # print(f"Palette already exists") return paletteKey, fPalette - if isPalRef: - fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) - return paletteKey, fPalette - paletteName, filename = getTextureNamesFromBasename(palBaseName, palFmt, parent, True) fPalette = FImage(paletteName, palFormat, "G_IM_SIZ_16b", 1, palLen, filename) @@ -2201,17 +2201,17 @@ def saveOrGetTextureDefinition( bitSize = texBitSizeF3D[texFmt] imageKey = getImageKey(texProp, images) + if texProp.use_tex_reference: + width, height = texProp.tex_reference_size + fImage = FImage(texProp.tex_reference, None, None, width, height, None) + return imageKey, fImage + # If image already loaded, return that data. fImage = parent.getTextureAndHandleShared(imageKey) if fImage is not None: # print(f"Image already exists") return imageKey, fImage - if texProp.use_tex_reference: - width, height = texProp.tex_reference_size - fImage = FImage(texProp.tex_reference, None, None, width, height, None) - return imageKey, fImage - imageName, filename = getTextureNamesFromProp(texProp, parent) fImage = FImage(imageName, texFormat, bitSize, image.size[0], image.size[1], filename) fImage.isLargeTexture = isLarge diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 225689e42..5cef8f40e 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -129,19 +129,22 @@ def getRenderMode(self, drawLayer): def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): model = self.getFlipbookOwner() + def raiseErr(submsg): + raise PluginError( + f"There are two flipbooks {subMsg} trying to write to the same texture array " + + f"named: {flipbook.name}.\nMake sure that this flipbook name is unique, or " + + "that repeated uses of this name use the same textures in the same order/format." + ) for existingFlipbook in model.flipbooks: if existingFlipbook.name == flipbook.name: if len(existingFlipbook.textureNames) != len(flipbook.textureNames): - raise PluginError( - f"There are two flipbooks with differing elements trying to write to the same texture array name: {flipbook.name}." - + f"\nMake sure that this flipbook name is unique, or that repeated uses of this name use the same textures is the same order/format." - ) + raiseErr(f"of different lengths ({len(existingFlipbook.textureNames)} " + + f"vs. {len(flipbook.textureNames)})") for i in range(len(flipbook.textureNames)): if existingFlipbook.textureNames[i] != flipbook.textureNames[i]: - raise PluginError( - f"There are two flipbooks with differing elements trying to write to the same texture array name: {flipbook.name}." - + f"\nMake sure that this flipbook name is unique, or that repeated uses of this name use the same textures is the same order/format." - ) + raiseErr(f"with differing elements (elem {i} = " + + f"{existingFlipbook.textureNames[i]} vs. " + + f"{flipbook.textureNames[i]})") model.flipbooks.append(flipbook) def validateImages(self, material: bpy.types.Material, index: int): @@ -199,8 +202,6 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbook.images.append((flipbookTexture.image, fImage_temp)) # print(f"Palette length: {len(pal)}") # Checked in getAndCheckTexInfo - - model.addFlipbookWithRepeatCheck(flipbook) return allImages, flipbook, pal def writeTexRefCITextures( @@ -220,11 +221,14 @@ def writeTexRefCITextures( imageKey = FImageKey(image, texFmt, palFmt, imagesSharingPalette) fImage = model.getTextureAndHandleShared(imageKey) if fImage is not None: + flipbook.textureNames[i] = fImage.name flipbook.images[i] = (image, fImage) else: fImage = fImage_temp model.addTexture(imageKey, fImage, fMaterial) writeCITextureData(image, fImage, pal, palFmt, texFmt) + # Have to delay this until here because texture names may have changed + model.addFlipbookWithRepeatCheck(flipbook) def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.Material, index: int): model = self.getFlipbookOwner() From ddb75cbf451a70a0a968a8b58822373e14c7ead0 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 12 Feb 2023 14:58:35 -0800 Subject: [PATCH 28/38] PR nearly done, just have to reorganize --- fast64_internal/f3d/f3d_material.py | 52 ++++++++++++++++++-------- fast64_internal/f3d/f3d_writer.py | 43 ++++++++++++++------- fast64_internal/f3d/op_largetexture.py | 22 +++++------ 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 69d4f671a..4f83f9d8b 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -219,8 +219,8 @@ def key(self): def getTmemWordUsage(texFormat, width, height): - texelsPerLine = 64 / texBitSizeInt[texFormat] - return int(math.ceil(width / texelsPerLine)) * height + texelsPerWord = 64 // texBitSizeInt[texFormat] + return (width + texelsPerWord - 1) // texelsPerWord * height def getTmemMax(texFormat): @@ -2277,9 +2277,28 @@ def ui_image( if tex is not None: prop_input.label(text="Size: " + str(tex.size[0]) + " x " + str(tex.size[1])) + if textureProp.use_tex_reference: + width, height = textureProp.tex_reference_size[0], textureProp.tex_reference_size[1] + elif tex is not None: + width, height = tex.size[0], tex.size[1] + else: + width = height = 0 + if canUseLargeTextures: - prop_input.row().label(text="Large texture mode enabled.") - prop_input.row().label(text="Recommend using Create Large Texture Mesh tool.") + availTmem = 512 + if textureProp.tex_format[:2] == "CI": + availTmem /= 2 + useDict = all_combiner_uses(material.f3d_mat) + if useDict["Texture 0"] and useDict["Texture 1"]: + availTmem /= 2 + isLarge = getTmemWordUsage(textureProp.tex_format, width, height) > availTmem + else: + isLarge = False + + if isLarge: + msg = prop_input.box().column() + msg.label(text="This is a large texture.", icon="INFO") + msg.label(text="Recommend using Create Large Texture Mesh tool.") else: tmemUsageUI(prop_input, textureProp) @@ -2287,7 +2306,20 @@ def ui_image( if textureProp.tex_format[:2] == "CI": prop_split(prop_input, textureProp, "ci_format", name="CI Format") - if not (canUseLargeTextures): + if not isLarge: + if width > 0 and height > 0: + texelsPerWord = 64 // texBitSizeInt[textureProp.tex_format] + if width % texelsPerWord != 0: + msg = prop_input.box().column() + msg.label(text=f"Suggest {textureProp.tex_format} tex be multiple ", icon="INFO") + msg.label(text=f"of {texelsPerWord} pixels wide for fast loading.") + warnClampS = not isPowerOf2(width) and not textureProp.S.clamp and (not textureProp.autoprop or textureProp.S.mask != 0) + warnClampT = not isPowerOf2(height) and not textureProp.T.clamp and (not textureProp.autoprop or textureProp.T.mask != 0) + if warnClampS or warnClampT: + msg = prop_input.box().column() + msg.label(text=f"Clamping required for non-power-of-2 image", icon="ERROR") + msg.label(text=f"dimensions. Enable clamp or set mask to 0.") + texFieldSettings = prop_input.column() clampSettings = texFieldSettings.row() clampSettings.prop(textureProp.S, "clamp", text="Clamp S") @@ -2316,16 +2348,6 @@ def ui_image( high.prop(textureProp.S, "high", text="S High") high.prop(textureProp.T, "high", text="T High") - if ( - tex is not None - and tex.size[0] > 0 - and tex.size[1] > 0 - and (math.log(tex.size[0], 2) % 1 > 0.000001 or math.log(tex.size[1], 2) % 1 > 0.000001) - ): - warnBox = layout.box() - warnBox.label(text="Warning: Texture dimensions are not power of 2.") - warnBox.label(text="Wrapping only occurs on power of 2 bounds.") - class CombinerProperty(bpy.types.PropertyGroup): A: bpy.props.EnumProperty( diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index aae3f6731..41a4f111d 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -266,23 +266,23 @@ def fixRegion(self, sl, sh, tl, th): if sh >= 1024 or th >= 1024: ret = False if sh >= self.texDimensions[0]: - # Load wraps in S. Load must start a multiple of a TMEM line from + # Load wraps in S. Load must start a multiple of a TMEM word from # the end of the texture, in order for the second load (beginning of - # image) to start at a whole line. - texelsPerLine = 64 // texBitSizeInt[self.texFormat] - if texelsPerLine > self.texDimensions[0]: + # image) to start at a whole word. + texelsPerWord = 64 // texBitSizeInt[self.texFormat] + if texelsPerWord > self.texDimensions[0]: raise PluginError( f"In large texture material {self.materialName}:" - + f" large texture must be at least {texelsPerLine} wide." + + f" large texture must be at least {texelsPerWord} wide." ) sl -= self.texDimensions[0] - sl = int(math.floor(sl / texelsPerLine)) * texelsPerLine + sl = int(math.floor(sl / texelsPerWord)) * texelsPerWord sl += self.texDimensions[0] if th >= self.texDimensions[1]: - # Load wraps in T. Load must start a multiple of 2 TMEM lines from + # Load wraps in T. Load must start a multiple of 2 texture rows from # the end of the texture, in order for the second load to have the - # same odd/even line parity as the first (because texels are - # interleaved in TMEM every other line). + # same odd/even row parity as the first (because texels are + # interleaved in TMEM every other row). tl -= self.texDimensions[1] tl = int(math.floor(tl / 2.0)) * 2 tl += self.texDimensions[1] @@ -366,7 +366,7 @@ def maybeSaveSingleLargeTextureSetup( # ) if wrapS or wrapT: fmt = texFormatOf[texProp.tex_format] - texelsPerLine = 64 // texBitSizeInt[texProp.tex_format] + texelsPerWord = 64 // texBitSizeInt[texProp.tex_format] wid = texDimensions[0] is4bit = siz == "G_IM_SIZ_4b" if is4bit: @@ -389,8 +389,8 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): if wrapS: # Break up at the wrap boundary into two tile loads. # The first load must occupy a whole number of lines. - assert (texDimensions[0] - tileSettings.sl) % texelsPerLine == 0 - sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerLine + assert (texDimensions[0] - tileSettings.sl) % texelsPerWord == 0 + sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerWord # print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") gfxOut.commands.append( DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) @@ -2342,6 +2342,23 @@ def getTileLine(fImage: FImage, SL: int, SH: int, siz: str, f3d: F3D): return line +def canUseLoadBlock(fImage: FImage, tex_format: str, f3d: F3D): + if fImage.isLargeTexture: + return False + width, height = fImage.width, fImage.height + texelsPerWord = 64 // texBitSizeInt[tex_format] + if width % texelsPerWord != 0: + return False + wordsperrow = width // texelsPerWord + dxt = ((1 << f3d.G_TX_DXT_FRAC) + wordsperrow - 1) // wordsperrow + error = (dxt * wordsperrow) - (1 << f3d.G_TX_DXT_FRAC) + assert error >= 0 + if error == 0: + return True + rowsWhenCorruptionHappens = (dxt + error - 1) // error + return height < rowsWhenCorruptionHappens + + def saveTextureLoadOnly( fImage: FImage, gfxOut: GfxList, @@ -2360,7 +2377,7 @@ def saveTextureLoadOnly( # LoadTile will pad rows to 64 bit word alignment, while # LoadBlock assumes this is already done. - useLoadBlock = not fImage.isLargeTexture and isPowerOf2(fImage.width) + useLoadBlock = canUseLoadBlock(fImage, texProp.tex_format, f3d) line = 0 if useLoadBlock else getTileLine(fImage, SL, SH, siz, f3d) wid = 1 if useLoadBlock else fImage.width diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index bacb22512..b45b0f0ce 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -139,23 +139,23 @@ def createLargeTextureMeshInternal(bm, prop): raise PluginError(err) (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info is4bit = texBitSizeInt[largeFmt] == 4 - texelsPerLine = 64 // texBitSizeInt[largeFmt] + texelsPerWord = 64 // texBitSizeInt[largeFmt] uvScale = [1.0 / largeDims[0], 1.0 / largeDims[1]] wrapSize = [prop.total_size_s, prop.total_size_t] # Set up base tile size if prop.bias == "Square": - maxTexelsInTMEM = largeWords * texelsPerLine - tileSLines = int(math.ceil(math.sqrt(maxTexelsInTMEM) / texelsPerLine)) + maxTexelsInTMEM = largeWords * texelsPerWord + tileSWords = int(math.ceil(math.sqrt(maxTexelsInTMEM) / texelsPerWord)) elif prop.bias == "Extreme": targetRows = 4 if bilinear else 2 # Start with just loading full texture rows, rounded up to lines - tileSLines = int(math.ceil(largeDims[0] / texelsPerLine)) - if largeWords // tileSLines < targetRows: + tileSWords = int(math.ceil(largeDims[0] / texelsPerWord)) + if largeWords // tileSWords < targetRows: # If that doesn't give us enough rows, reduce to next power of 2 d = 1 << int(math.floor(math.log2(largeDims[0]))) - tileSLines = d // texelsPerLine - while largeWords // tileSLines < targetRows: - tileSLines >>= 1 + tileSWords = d // texelsPerWord + while largeWords // tileSWords < targetRows: + tileSWords >>= 1 else: baseTile = [128, 64] while True: @@ -174,8 +174,8 @@ def createLargeTextureMeshInternal(bm, prop): # Even though we have baseTile already, convert back to this format, # in case available TMEM is not a power of 2 we might get a larger T value. # (Currently the plugin will always assign a power of 2 size) - tileSLines = baseTile[0] // texelsPerLine - baseTile = [tileSLines * texelsPerLine, largeWords // tileSLines] + tileSWords = baseTile[0] // texelsPerWord + baseTile = [tileSWords * texelsPerWord, largeWords // tileSWords] if bilinear: baseTile[0] -= 1 if is4bit: @@ -227,7 +227,7 @@ def wrapGridDim(dim): # produce a mesh with verts on other edges, and the N64 does not guarantee # that these meshes won't have holes in them. Prefer correct seamless results # over saving a few tri draws (the loads will still be combined). - distFromWrap = (texelsPerLine, 2)[dim] + distFromWrap = (texelsPerWord, 2)[dim] # Number of texels such that if a wrap load could reach the end of the drawn # region by continuing to load this many texels into the image after wrapping, # it's worth it to do so (as opposed to only loading row/col 0, and drawing From 7fad88d238c979605f2896234c758d7ffdb90f4b Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 12 Feb 2023 18:04:52 -0800 Subject: [PATCH 29/38] Draft of refactor --- fast64_internal/f3d/f3d_texture_writer.py | 1338 +++++++++++++++++++++ fast64_internal/f3d/f3d_writer.py | 1332 +------------------- fast64_internal/f3d/op_largetexture.py | 55 +- fast64_internal/oot/oot_model_classes.py | 12 +- fast64_internal/sm64/sm64_f3d_writer.py | 37 +- 5 files changed, 1394 insertions(+), 1380 deletions(-) create mode 100644 fast64_internal/f3d/f3d_texture_writer.py diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py new file mode 100644 index 000000000..cc9a3b36e --- /dev/null +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -0,0 +1,1338 @@ +from typing import Union, Optional +from dataclasses import dataclass +import bpy +from math import ceil, floor + +from .f3d_enums import * +from .f3d_constants import * +from .f3d_material import ( + all_combiner_uses, + getTmemWordUsage, + texBitSizeF3D, + texFormatOf, + TextureProperty, + F3DMaterialProperty, +) +from .f3d_gbi import * +from .f3d_gbi import _DPLoadTextureBlock + +from ..utility import * + + +class TileLoad: + def __init__(self, material, fMaterial, texDimensions): + self.sl = self.tl = 1000000 # above any actual value + self.sh = self.th = -1 # below any actual value + + self.texFormat = fMaterial.largeTexFmt + self.is4bit = texBitSizeInt[self.texFormat] == 4 + self.tmemWordsAvail = fMaterial.largeTexWords + self.texDimensions = texDimensions + self.materialName = material.name + self.isPointSampled = isTexturePointSampled(material) + self.largeEdges = material.f3d_mat.large_edges + + self.faces = [] + self.offsets = [] + + def getLow(self, value, field): + value = int(floor(value)) + if self.largeEdges == "Clamp": + value = min(max(value, 0), self.texDimensions[field] - 1) + if self.is4bit and field == 0: + # Must start on an even texel (round down) + value &= ~1 + return value + + def getHigh(self, value, field): + value = int(ceil(value)) - (1 if self.isPointSampled else 0) + if self.largeEdges == "Clamp": + value = min(max(value, 0), self.texDimensions[field] - 1) + if self.is4bit and field == 0: + # Must end on an odd texel (round up) + value |= 1 + return value + + def fixRegion(self, sl, sh, tl, th): + assert sl <= sh and tl <= th + soffset = int(floor(sl / self.texDimensions[0])) * self.texDimensions[0] + toffset = int(floor(tl / self.texDimensions[1])) * self.texDimensions[1] + sl -= soffset + sh -= soffset + tl -= toffset + th -= toffset + assert 0 <= sl < self.texDimensions[0] and 0 <= tl < self.texDimensions[1] + ret = True + if sh >= 1024 or th >= 1024: + ret = False + if sh >= self.texDimensions[0]: + # Load wraps in S. Load must start a multiple of a TMEM word from + # the end of the texture, in order for the second load (beginning of + # image) to start at a whole word. + texelsPerWord = 64 // texBitSizeInt[self.texFormat] + if texelsPerWord > self.texDimensions[0]: + raise PluginError( + f"In large texture material {self.materialName}:" + + f" large texture must be at least {texelsPerWord} wide." + ) + sl -= self.texDimensions[0] + sl = int(floor(sl / texelsPerWord)) * texelsPerWord + sl += self.texDimensions[0] + if th >= self.texDimensions[1]: + # Load wraps in T. Load must start a multiple of 2 texture rows from + # the end of the texture, in order for the second load to have the + # same odd/even row parity as the first (because texels are + # interleaved in TMEM every other row). + tl -= self.texDimensions[1] + tl = int(floor(tl / 2.0)) * 2 + tl += self.texDimensions[1] + tmemUsage = getTmemWordUsage(self.texFormat, sh - sl + 1, th - tl + 1) + if tmemUsage > self.tmemWordsAvail: + ret = False + return ret, sl, sh, tl, th, soffset, toffset + + def initWithFace(self, obj, face): + uv_data = obj.data.uv_layers["UVMap"].data + faceUVs = [UVtoSTLarge(obj, loopIndex, uv_data, self.texDimensions) for loopIndex in face.loops] + if len(faceUVs) == 0: + return True + + for point in faceUVs: + self.sl = min(self.sl, self.getLow(point[0], 0)) + self.sh = max(self.sh, self.getHigh(point[0], 0)) + self.tl = min(self.tl, self.getLow(point[1], 1)) + self.th = max(self.th, self.getHigh(point[1], 1)) + + ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion(self.sl, self.sh, self.tl, self.th) + if not ret: + if self.sh >= 1024 or self.th >= 1024: + raise PluginError( + f"Large texture material {self.materialName} has a face that needs" + + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" + + f" (image dims are {self.texDimensions}), but image space" + + f" only goes up to 1024 so this cannot be represented." + ) + else: + raise PluginError( + f"Large texture material {self.materialName} has a face that needs" + + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" + + f" ({self.sh-self.sl+1} x {self.th-self.tl+1} texels) " + + f"in format {self.texFormat}, which can't fit in TMEM." + ) + self.faces.append(face) + self.offsets.append((soffset, toffset)) + + def trySubsume(self, other): + # Could do fancier logic checking across borders, for example if we have + # one loading 60-68 (size 64) and another 0-8, that could be merged to + # one load 60-72. But this is likely to be uncommon and won't be generated + # by the operator. + new_sl = min(self.sl, other.sl) + new_sh = max(self.sh, other.sh) + new_tl = min(self.tl, other.tl) + new_th = max(self.th, other.th) + ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = self.fixRegion(new_sl, new_sh, new_tl, new_th) + if not ret: + return False + self.sl, self.sh, self.tl, self.th = new_sl, new_sh, new_tl, new_th + self.faces.extend(other.faces) + self.offsets.extend(other.offsets) + return True + + +def maybeSaveSingleLargeTextureSetup( + i: int, + fMaterial: FMaterial, + fModel: FModel, + fImage: FImage, + gfxOut: GfxList, + texProp: TextureProperty, + texDimensions: tuple[int, int], + tileSettings: TileLoad, + curImgSet: Optional[int], + curTileLines: list[int], +): + if fMaterial.isTexLarge[i]: + wrapS = tileSettings.sh >= texDimensions[0] + wrapT = tileSettings.th >= texDimensions[1] + assert 0 <= tileSettings.sl < texDimensions[0] + assert 0 <= tileSettings.tl < texDimensions[1] + siz = texBitSizeF3D[texProp.tex_format] + line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) + tmem = fMaterial.largeTexAddr[i] + # print( + # f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " + # + f"tmem {tmem} line {line}" + # ) + if wrapS or wrapT: + fmt = texFormatOf[texProp.tex_format] + texelsPerWord = 64 // texBitSizeInt[texProp.tex_format] + wid = texDimensions[0] + is4bit = siz == "G_IM_SIZ_4b" + if is4bit: + siz = "G_IM_SIZ_8b" + wid >>= 1 + assert (tileSettings.sl & 1) == 0 + assert (tileSettings.sh & 1) == 1 + # TL, TH is always * 4 because tile values are 10.2 fixed. + # SL, SH is * 2 for 4 bit and * 4 otherwise, because actually loading + # 8 bit pairs of texels. Also written using f3d.G_TEXTURE_IMAGE_FRAC. + sm = 2 if is4bit else 4 + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + if curImgSet != i: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + + def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): + if line != curTileLines[tidxBase]: + gfxOut.commands.append(DPSetTile(fmt, siz, line, tmemBase, tidxBase, 0, nocm, 0, 0, nocm, 0, 0)) + curTileLines[tidxBase] = line + if wrapS: + # Break up at the wrap boundary into two tile loads. + # The first load must occupy a whole number of lines. + assert (texDimensions[0] - tileSettings.sl) % texelsPerWord == 0 + sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerWord + # print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") + gfxOut.commands.append( + DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) + ) + gfxOut.commands.append( + DPSetTile(fmt, siz, line, tmemBase + sLineOfs, tidxBase - 1, 0, nocm, 0, 0, nocm, 0, 0) + ) + curTileLines[tidxBase - 1] = -1 + gfxOut.commands.append( + DPLoadTile(tidxBase - 1, 0, TL * 4, (tileSettings.sh - texDimensions[0]) * sm, TH * 4) + ) + else: + gfxOut.commands.append( + DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, tileSettings.sh * sm, TH * 4) + ) + + if wrapT: + # Break up at the wrap boundary into two loads. + # The first load must be even in size (even number of texture rows). + assert (texDimensions[1] - tileSettings.tl) % 2 == 0 + tLineOfs = line * (texDimensions[1] - tileSettings.tl) + # print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") + loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) + loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) + else: + loadOneOrTwoS(tmem, 7, tileSettings.tl, tileSettings.th) + if fMaterial.isTexLarge[i ^ 1]: + # May reuse any of the above tiles for the other large texture. + gfxOut.commands.append(DPTileSync()) + else: + saveTextureLoadOnly( + fImage, + gfxOut, + texProp, + tileSettings, + 7 - i, + tmem, + fModel.f3d, + curImgSet == i, + line == curTileLines[7 - i], + ) + curTileLines[7 - i] = line + curImgSet = i + saveTextureTile( + fImage, + fMaterial, + gfxOut, + texProp, + tileSettings, + i, + tmem, + fMaterial.texPaletteIndex[i], + fModel.f3d, + line == curTileLines[i], + ) + curTileLines[i] = line + return curImgSet + + +# Functions for texture and palette definitions + + +def getTextureNamesFromBasename( + baseName: str, + texOrPalFormat: str, + parent: Union[FModel, FTexRect], + isPalette: bool +): + suffix = getTextureSuffixFromFormat(texOrPalFormat) + imageName = parent.name + "_" + baseName + "_" + if isPalette: + imageName += "pal_" + imageName += suffix + imageName = checkDuplicateTextureName(parent, toAlnum(imageName)) + filename = baseName + "." + suffix + (".pal" if isPalette else ".inc.c") + return imageName, filename + + +def getImageName(image: bpy.types.Image): + if image is None: + raise PluginError("No image set in material!") + elif image.filepath == "": + return image.name + else: + return getNameFromPath(image.filepath, True) + + +def getTextureNamesFromImage( + image: bpy.types.Image, + texFormat: str, + parent: Union[FModel, FTexRect] +): + return getTextureNamesFromBasename(getImageName(image), texFormat, parent, False) + + +def getTextureNamesFromProp( + texProp: TextureProperty, + parent: Union[FModel, FTexRect] +): + if texProp.use_tex_reference: + raise PluginError("Internal error, invalid use of getTextureNamesFromProp") + return getTextureNamesFromImage(texProp.tex, texProp.tex_format, parent) + + + +def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): + names = [] + for info, texture in parent.textures.items(): + names.append(texture.name) + while name in names: + name = name + "_copy" + return name + + +def saveOrGetPaletteDefinition( + fMaterial: FMaterial, + parent: Union[FModel, FTexRect], + texProp: TextureProperty, + isPalRef: bool, + images: list[bpy.types.Image], + palBaseName: str, + palLen: int, +) -> tuple[FPaletteKey, FImage]: + + texFmt = texProp.tex_format + palFmt = texProp.ci_format + palFormat = texFormatOf[palFmt] + paletteKey = FPaletteKey(palFmt, images) + + if isPalRef: + fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) + return paletteKey, fPalette + + # If palette already loaded, return that data. + fPalette = parent.getTextureAndHandleShared(paletteKey) + if fPalette is not None: + # print(f"Palette already exists") + return paletteKey, fPalette + + paletteName, filename = getTextureNamesFromBasename(palBaseName, palFmt, parent, True) + fPalette = FImage(paletteName, palFormat, "G_IM_SIZ_16b", 1, palLen, filename) + + parent.addTexture(paletteKey, fPalette, fMaterial) + return paletteKey, fPalette + + +def saveOrGetTextureDefinition( + fMaterial: FMaterial, + parent: Union[FModel, FTexRect], + texProp: TextureProperty, + images: list[bpy.types.Image], + isLarge: bool, +) -> tuple[FImageKey, FImage]: + + image = texProp.tex + texFmt = texProp.tex_format + texFormat = texFormatOf[texFmt] + bitSize = texBitSizeF3D[texFmt] + imageKey = getImageKey(texProp, images) + + if texProp.use_tex_reference: + width, height = texProp.tex_reference_size + fImage = FImage(texProp.tex_reference, None, None, width, height, None) + return imageKey, fImage + + # If image already loaded, return that data. + fImage = parent.getTextureAndHandleShared(imageKey) + if fImage is not None: + # print(f"Image already exists") + return imageKey, fImage + + imageName, filename = getTextureNamesFromProp(texProp, parent) + fImage = FImage(imageName, texFormat, bitSize, image.size[0], image.size[1], filename) + fImage.isLargeTexture = isLarge + + parent.addTexture(imageKey, fImage, fMaterial) + return imageKey, fImage + + +@dataclass +class TexInfo(): + # Main parameters + useTex: bool = False + isTexRef: bool = False + isTexCI: bool = False + texFormat: str = "" + palFormat: str = "" + imageDims: tuple[int, int] = (0, 0) + tmemSize: int = 0 + errorMsg: str = "" + + # Parameters from moreSetupFromModel + pal: Optional[TODO] = None + palLen: int = 0 + imUse: list[bpy.types.Image] = [] + flipbook: Optional[TODO] = None + isPalRef: bool = False + + # Parameters computed by MultitexManager.writeAll + texAddr: int = 0 + palAddr: int = 0 + palIndex: int = 0 + palUse: list[bpy.types.Image] = [] + palBaseName: str = "" + loadPal: bool = False + doTexLoad: bool = True + doTexTile: bool = True + + # Internal parameters--copies of passed parameters + texProp: Optional[TextureProperty] = None + indexInMat: int = -1 + + def fromMat(self, index: int, f3dMat: F3DMaterialProperty) -> bool: + useDict = all_combiner_uses(f3dMat) + if not useDict["Texture " + str(index)]: + return True + + texProp = getattr(f3dMat, "tex" + str(index)) + return self.fromProp(texProp, index) + + def fromProp(self, texProp: TextureProperty, index: int) -> bool: + self.indexInMat = index + self.texProp = texProp + if not texProp.tex_set: + return True + + self.useTex = True + tex = texProp.tex + self.isTexRef = texProp.use_tex_reference + self.texFormat = texProp.tex_format + self.isTexCI = self.texFormat[:2] == "CI" + self.palFormat = texProp.ci_format if self.isTexCI else "" + + if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0): + self.errorMsg = f"Image {tex.name} has 0 size; may have been deleted/moved." + return False + + if not self.isTexRef: + if tex is None: + self.errorMsg = f"No texture is selected." + return False + elif len(tex.pixels) == 0: + self.errorMsg = f"Image {tex.name} is missing on disk." + return False + + if self.isTexRef: + width, height = texProp.tex_reference_size + else: + width, height = tex.size + self.imageDims = (width, height) + + self.tmemSize = getTmemWordUsage(self.texFormat, width, height) + + if width > 1024 or height > 1024: + self.errorMsg = f"Image size (even large textures) limited to 1024 in each dimension." + return False + + if texBitSizeInt[self.texFormat] == 4 and (width & 1) != 0: + self.errorMsg = f"A 4-bit image must have a width which is even." + return False + + return True + + def moreSetupFromModel( + material: bpy.types.Material, + fMaterial: FMaterial, + fModel: FModel, + ) -> None: + if not self.useTex: + return + + if self.isTexCI: + self.imUse, self.flipbook, self.pal = fModel.processTexRefCITextures( + fMaterial, material, self.indexInMat) + if self.isTexRef: + if self.flipbook is not None: + self.palLen = len(self.pal) + else: + self.palLen = self.texProp.pal_reference_size + else: + assert self.flipbook is None + self.pal = getColorsUsedInImage(self.texProp.tex, self.palFormat) + self.palLen = len(self.pal) + if self.palLen > (16 if self.texFormat == "CI4" else 256): + raise PluginError( + f"Error in {material.name}: texture {self.indexInMat}" + + (" (all flipbook textures)" if self.flipbook is not None else "") + + f" uses too many unique colors to fit in format {self.texFormat}." + ) + else: + self.imUse, self.flipbook = fModel.processTexRefNonCITextures( + fMaterial, material, self.indexInMat) + + self.isPalRef = self.isTexRef and self.flipbook is None + self.palUse = self.imUse + + def getPaletteName(self): + if not self.useTex or self.isPalRef: + return None + if self.flipbook is not None: + return self.flipbook.name + return getImageName(self.texProp.tex) + + def writeAll(self, + loadGfx: GfxList, + fMaterial: FMaterial, + fModel: Union[FModel, FTexRect], + convertTextureData: bool, + ): + if not self.useTex: + return + assert len(self.imUse) > 0 # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] + + # Get definitions + imageKey, fImage = saveOrGetTextureDefinition( + fMaterial, fModel, self.texProp, self.imUse, fMaterial.isTexLarge[self.indexInMat] + ) + fMaterial.imageKey[indexInMat] = imageKey + if self.loadPal: + _, fPalette = saveOrGetPaletteDefinition( + fMaterial, fModel, self.texProp, self.isPalRef, self.palUse, self.palBaseName, self.palLen + ) + + # Write loads + loadGfx = fMaterial.material + f3d = fModel.f3d + if self.loadPal: + savePaletteLoad( + loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) + if self.doTexLoad: + saveTextureLoadOnly( + fImage, loadGfx, self.texProp, None, 7 - self.indexInMat, self.texAddr, f3d) + if self.doTexTile: + saveTextureTile( + fImage, fMaterial, loadGfx, self.texProp, None, self.indexInMat, self.texAddr, self.palIndex, f3d) + + # Write texture data + if convertTextureData: + if self.loadPal and not self.isPalRef: + writePaletteData(fPalette, self.pal) + if self.isTexRef: + if self.isTexCI: + fModel.writeTexRefCITextures(self.flipbook, fMaterial, self.imUse, self.pal, self.texFormat, self.palFormat) + else: + fModel.writeTexRefNonCITextures(self.flipbook, self.texFormat) + else: + if self.isTexCI: + writeCITextureData(self.texProp.tex, fImage, self.pal, self.palFormat, self.texFormat) + else: + writeNonCITextureData(self.texProp.tex, fImage, self.texFormat) + + +class MultitexManager(): + def __init__(self, + material: bpy.types.Material, + fMaterial: FMaterial, + fModel: FModel, + ): + f3dMat = material.f3d_mat + self.ti0, self.ti1 = TexInfo(), TexInfo() + if not self.ti0.fromMat(0, f3dMat): + raise PluginError(f"In {material.name} tex0: {self.ti0.errorMsg}") + if not self.ti1.fromMat(1, f3dMat): + raise PluginError(f"In {material.name} tex1: {self.ti1.errorMsg}") + self.ti0.moreSetupFromModel(material, fMaterial, fModel) + self.ti1.moreSetupFromModel(material, fMaterial, fModel) + + self.isCI = self.ti0.isTexCI or self.ti1.isTexCI + + if self.ti0.useTex and self.ti1.useTex: + if self.ti0.isTexCI != self.ti1.isTexCI: + raise PluginError( + "In material " + + material.name + + ": N64 does not support CI + non-CI texture. " + + "Must be both CI or neither CI." + ) + if ( + self.ti0.isTexRef + and self.ti1.isTexRef + and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference + and self.ti0.texProp.tex_reference_size != self.ti1.texProp.tex_reference_size + ): + raise PluginError( + "In material " + material.name + ": Two textures with the same reference must have the same size." + ) + if self.isCI: + if self.ti0.palFormat != self.ti1.palFormat: + raise PluginError( + "In material " + + material.name + + ": Both CI textures must use the same palette format (usually RGBA16)." + ) + if ( + self.ti0.isTexRef + and self.ti1.isTexRef + and self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference + and self.ti0.texProp.pal_reference_size != self.ti1.texProp.pal_reference_size + ): + raise PluginError( + "In material " + + material.name + + ": Two textures with the same palette reference must have the same palette size." + ) + + self.palFormat = self.ti0.palFormat if self.ti0.useTex else self.ti1.palFormat + + def getTT(self) -> str: + return "G_TT_NONE" if not self.isCI else ("G_TT_" + self.palFormat) + + def writeAll(self, + material: bpy.types.Material, + fMaterial: FMaterial, + fModel: FModel, + convertTextureData: bool + ) -> None: + f3dMat = material.f3d_mat + # Determine how to arrange / load palette entries into upper half of tmem + if self.isCI: + assert self.ti0.useTex or self.ti1.useTex + if not self.ti1.useTex: + self.ti0.loadPal = True + elif not self.ti0.useTex: + self.ti1.loadPal = True + elif not convertTextureData: + if self.ti0.texFormat == "CI8" or self.ti1.texFormat == "CI8": + raise PluginError( + "In material " + + material.name + + ": When using export as PNGs mode, can't have multitexture with one or more CI8 textures." + + " Only single CI texture or two CI4 textures." + ) + self.ti0.loadPal = self.ti1.loadPal = True + self.ti1.palIndex = 1 + self.ti1.palAddr = 16 + else: # Two CI textures, normal mode + if self.ti0.texFormat == "CI8" and self.ti1.texFormat == "CI8": + if (self.ti0.pal is None) != (self.ti1.pal is None): + raise PluginError( + "In material " + + material.name + + ": can't have two CI8 textures where only one is a non-flipbook reference; " + + "no way to assign the palette." + ) + self.ti0.loadPal = True + if self.ti0.pal is None: + if self.ti0.texProp.pal_reference != self.ti1.texProp.pal_reference: + raise PluginError( + "In material " + + material.name + + ": can't have two CI8 textures with different palette references." + ) + else: + self.ti0.pal = mergePalettes(self.ti0.pal, self.ti1.pal) + self.ti0.palLen = len(self.ti0.pal) + if self.ti0.palLen > 256: + raise PluginError( + "In material " + + material.name + + ": the two CI textures together contain a total of " + + str(self.ti0.palLen) + + " colors, which can't fit in a CI8 palette (256)." + ) + # self.ti0.imUse remains what it was; the CIs in im0 are the same as they + # would be if im0 was alone. But im1 and self.ti0.pal depend on both. + self.ti1.imUse = self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + elif self.ti0.texFormat != self.ti1.texFormat: # One CI8, one CI4 + ci8Pal, ci4Pal = (self.ti0.pal, self.ti1.pal) if self.ti0.texFormat == "CI8" else (self.ti1.pal, self.ti0.pal) + ci8PalLen, ci4PalLen = (self.ti0.palLen, self.ti1.palLen) if self.ti0.texFormat == "CI8" else (self.ti1.palLen, self.ti0.palLen) + if self.ti0.pal is None or self.ti1.pal is None: + if ci8PalLen > 256 - 16: + raise PluginError( + "In material " + + material.name + + ": the CI8 texture has over 240 colors, which can't fit together with the CI4 palette." + ) + self.ti0.loadPal = self.ti1.loadPal = True + if self.ti0.texFormat == "CI8": + self.ti1.palIndex = 15 + self.ti1.palAddr = 240 + else: + self.ti0.palIndex = 15 + self.ti0.palAddr = 240 + else: + # CI4 indices in palette 0, CI8 indices start from palette 0 + self.ti0.loadPal = True + self.ti0.pal = mergePalettes(ci4Pal, ci8Pal) + self.ti0.palLen = len(self.ti0.pal) + if self.ti0.palLen > 256: + raise PluginError( + "In material " + + material.name + + ": the two CI textures together contain a total of " + + str(self.ti0.palLen) + + " colors, which can't fit in a CI8 palette (256)." + + " The CI8 texture must contain up to 240 unique colors," + + " plus the same up to 16 colors used in the CI4 texture." + ) + # The use for the CI4 texture remains what it was; its CIs are the + # same as if it was alone. But both the palette and the CI8 CIs are affected. + self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + if self.ti0.texFormat == "CI8": + self.ti0.imUse = self.ti0.palUse + else: + self.ti1.imUse = self.ti0.palUse + else: # both CI4 textures + if self.ti0.pal is None and self.ti1.pal is None and self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference: + self.ti0.loadPal = True + elif self.ti0.pal is None or self.ti1.pal is None: + self.ti0.loadPal = self.ti1.loadPal = True + self.ti1.palIndex = 1 + self.ti1.palAddr = 16 + else: + self.ti0.loadPal = True + tempPal = mergePalettes(self.ti0.pal, self.ti1.pal) + tempPalLen = len(tempPal) + assert tempPalLen <= 32 + if tempPalLen <= 16: + # Share palette 0 + self.ti0.pal = tempPal + self.ti0.palLen = tempPalLen + # self.ti0.imUse remains what it was; the CIs in im0 are the same as they + # would be if im0 was alone. But im1 and self.ti0.pal depend on both. + self.ti1.imUse = self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + else: + # Load one palette across 0-1. Put the longer in slot 0 + if self.ti0.palLen >= self.ti1.palLen: + while len(self.ti0.pal) < 16: + self.ti0.pal.append(0) + self.ti0.pal.extend(self.ti1.pal) + self.ti0.palLen = len(self.ti0.pal) + self.ti1.palIndex = 1 + else: + while len(self.ti1.pal) < 16: + self.ti1.pal.append(0) + self.ti0.pal = self.ti1.pal + self.ti0.pal + self.ti0.palLen = len(self.ti0.pal) + self.ti0.palIndex = 1 + # The up-to-32 entries in self.ti0.pal depend on both images. But the + # CIs in both im0 and im1 are the same as if there was no shared palette. + self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + fMaterial.texPaletteIndex = [self.ti0.palIndex, self.ti1.palIndex] + self.ti0.palBaseName = self.ti0.getPaletteName() + self.ti1.palBaseName = self.ti1.getPaletteName() + if self.isCI and self.ti0.useTex and self.ti1.useTex and not self.ti1.loadPal: + self.ti0.palBaseName = self.ti0.palBaseName + "_x_" + self.ti1.palBaseName + self.ti1.pal = self.ti0.pal + + # Assign TMEM addresses + sameTextures = ( + self.ti0.useTex + and self.ti1.useTex + and ( + (not self.ti0.isTexRef and not self.ti1.isTexRef and self.ti0.texProp.tex == self.ti1.texProp.tex) + or (self.ti0.isTexRef and self.ti1.isTexRef and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference) + ) + ) + useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures + tmemSize = 256 if self.isCI else 512 + self.ti1.texAddr = None # must be set whenever tex 1 used (and loaded or tiled) + tmemOccupied = self.texDimensions = None # must be set on all codepaths + if sameTextures: + assert self.ti0.tmemSize == self.ti1.tmemSize + tmemOccupied = self.ti0.tmemSize + self.ti1.doTexLoad = False + self.ti1.texAddr = 0 + self.texDimensions = self.ti0.imageDims + fMaterial.largeTexFmt = self.ti0.texFormat + elif not useLargeTextures or self.ti0.tmemSize + self.ti1.tmemSize <= tmemSize: + self.ti1.texAddr = self.ti0.tmemSize + tmemOccupied = self.ti0.tmemSize + self.ti1.tmemSize + if not self.ti0.useTex and not self.ti1.useTex: + self.texDimensions = [32, 32] + fMaterial.largeTexFmt = "RGBA16" + elif not self.ti1.useTex or f3dMat.uv_basis == "TEXEL0": + self.texDimensions = self.ti0.imageDims + fMaterial.largeTexFmt = self.ti0.texFormat + else: + self.texDimensions = self.ti1.imageDims + fMaterial.largeTexFmt = self.ti1.texFormat + else: # useLargeTextures + if self.ti0.useTex and self.ti1.useTex: + tmemOccupied = tmemSize + # TODO: Could change this in the future to do the face tile assigments + # first, to see how large a tile the large texture(s) needed, instead + # of arbitrarily assigning half of TMEM to each of the two textures. + if self.ti0.tmemSize <= tmemSize // 2: + # Tex 0 normal, tex 1 large + self.texDimensions = self.ti1.imageDims + fMaterial.largeTexFmt = self.ti1.texFormat + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[1] = self.ti0.tmemSize + fMaterial.largeTexWords = tmemSize - self.ti0.tmemSize + self.ti1.doTexLoad = self.ti1.doTexTile = False + elif self.ti1.tmemSize <= tmemSize // 2: + # Tex 0 large, tex 1 normal + self.texDimensions = self.ti0.imageDims + fMaterial.largeTexFmt = self.ti0.texFormat + fMaterial.isTexLarge[0] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexWords = tmemSize - self.ti1.tmemSize + self.ti0.doTexLoad = self.ti0.doTexTile = False + self.ti1.texAddr = tmemSize - self.ti1.tmemSize + else: + # Both textures large + raise PluginError( + 'Error in "' + material.name + '": Multitexture with two large textures is not currently supported.' + ) + # Limited cases of 2x large textures could be supported in the + # future. However, these cases are either of questionable + # utility or have substantial restrictions. Most cases could be + # premixed into one texture, or would run out of UV space for + # tiling (1024x1024 in the space of whichever texture had + # smaller pixels), or one of the textures could be non-large. + if f3dMat.uv_basis == "TEXEL0": + self.texDimensions = self.ti0.imageDims + fMaterial.largeTexFmt = self.ti0.texFormat + else: + self.texDimensions = self.ti1.imageDims + fMaterial.largeTexFmt = self.ti1.texFormat + fMaterial.isTexLarge[0] = True + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexAddr[1] = tmemSize // 2 + fMaterial.largeTexWords = tmemSize // 2 + self.ti0.doTexLoad = self.ti0.doTexTile = self.ti1.doTexLoad = self.ti1.doTexTile = False + elif self.ti0.useTex: + self.texDimensions = self.ti0.imageDims + fMaterial.largeTexFmt = self.ti0.texFormat + fMaterial.isTexLarge[0] = True + fMaterial.largeTexAddr[0] = 0 + fMaterial.largeTexWords = tmemSize + self.ti0.doTexLoad = self.ti0.doTexTile = False + tmemOccupied = tmemSize + elif self.ti1.useTex: + self.ti1.texAddr = 0 + self.texDimensions = self.ti1.imageDims + fMaterial.largeTexFmt = self.ti1.texFormat + fMaterial.isTexLarge[1] = True + fMaterial.largeTexAddr[1] = 0 + fMaterial.largeTexWords = tmemSize + self.ti1.doTexLoad = self.ti1.doTexTile = False + tmemOccupied = tmemSize + if tmemOccupied > tmemSize: + if sameTextures and useLargeTextures: + raise PluginError( + 'Error in "' + + material.name + + '": Using the same texture for Tex0 and Tex1 is not compatible with large textures.' + ) + elif not bpy.context.scene.ignoreTextureRestrictions: + raise PluginError( + 'Error in "' + + material.name + + '": Textures are too big. Max TMEM size is 4k ' + + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." + ) + + self.ti0.writeAll(fMaterial.material, fMaterial, fModel, convertTextureData) + self.ti1.writeAll(fMaterial.material, fMaterial, fModel, convertTextureData) + + def getTexDimensions(self): + return self.texDimensions + + +# Functions for writing texture and palette DLs + + +def getTileSizeSettings(texProp: TextureProperty, tileSettings: Optional[TileLoad], f3d: F3D): + if tileSettings is not None: + SL = tileSettings.sl + TL = tileSettings.tl + SH = tileSettings.sh + TH = tileSettings.th + else: + SL = texProp.S.low + TL = texProp.T.low + SH = texProp.S.high + TH = texProp.T.high + sl = int(SL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) + tl = int(TL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) + sh = int(SH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) + th = int(TH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) + return SL, TL, SH, TH, sl, tl, sh, th + + +def getTileLine(fImage: FImage, SL: int, SH: int, siz: str, f3d: F3D): + width = int(SH - SL + 1) if fImage.isLargeTexture else int(fImage.width) + if siz == "G_IM_SIZ_4b": + line = (((width + 1) >> 1) + 7) >> 3 + else: + # Note that _LINE_BYTES and _TILE_BYTES variables are the same. + line = int((width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 + return line + + +def canUseLoadBlock(fImage: FImage, tex_format: str, f3d: F3D): + if fImage.isLargeTexture: + return False + width, height = fImage.width, fImage.height + texelsPerWord = 64 // texBitSizeInt[tex_format] + if width % texelsPerWord != 0: + return False + wordsperrow = width // texelsPerWord + dxt = ((1 << f3d.G_TX_DXT_FRAC) + wordsperrow - 1) // wordsperrow + error = (dxt * wordsperrow) - (1 << f3d.G_TX_DXT_FRAC) + assert error >= 0 + if error == 0: + return True + rowsWhenCorruptionHappens = (dxt + error - 1) // error + return height < rowsWhenCorruptionHappens + + +def saveTextureLoadOnly( + fImage: FImage, + gfxOut: GfxList, + texProp: TextureProperty, + tileSettings: Optional[TileLoad], + loadtile: int, + tmem: int, + f3d: F3D, + omitSetTextureImage=False, + omitSetTile=False, +): + fmt = texFormatOf[texProp.tex_format] + siz = texBitSizeF3D[texProp.tex_format] + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) + + # LoadTile will pad rows to 64 bit word alignment, while + # LoadBlock assumes this is already done. + useLoadBlock = canUseLoadBlock(fImage, texProp.tex_format, f3d) + line = 0 if useLoadBlock else getTileLine(fImage, SL, SH, siz, f3d) + wid = 1 if useLoadBlock else fImage.width + + if siz == "G_IM_SIZ_4b": + if useLoadBlock: + dxs = (((fImage.width) * (fImage.height) + 3) >> 2) - 1 + dxt = f3d.CALC_DXT_4b(fImage.width) + siz = "G_IM_SIZ_16b" + loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) + else: + sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) + siz = "G_IM_SIZ_8b" + wid >>= 1 + loadCommand = DPLoadTile(loadtile, sl2, tl, sh2, th) + else: + if useLoadBlock: + dxs = ( + ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) + >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] + ) - 1 + dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) + siz += "_LOAD_BLOCK" + loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) + else: + loadCommand = DPLoadTile(loadtile, sl, tl, sh, th) + + if not omitSetTextureImage: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + if not omitSetTile: + gfxOut.commands.append(DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) + gfxOut.commands.append(loadCommand) + + +def saveTextureTile( + fImage: FImage, + fMaterial: FMaterial, + gfxOut: GfxList, + texProp: TextureProperty, + tileSettings, + rendertile: int, + tmem: int, + pal: int, + f3d: F3D, + omitSetTile=False, +): + if tileSettings is not None: + clamp_S = True + clamp_T = True + mirror_S = False + mirror_T = False + mask_S = 0 + mask_T = 0 + shift_S = 0 + shift_T = 0 + else: + clamp_S = texProp.S.clamp + clamp_T = texProp.T.clamp + mirror_S = texProp.S.mirror + mirror_T = texProp.T.mirror + mask_S = texProp.S.mask + mask_T = texProp.T.mask + shift_S = texProp.S.shift + shift_T = texProp.T.shift + cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] + cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] + masks = mask_S + maskt = mask_T + shifts = shift_S if shift_S >= 0 else (shift_S + 16) + shiftt = shift_T if shift_T >= 0 else (shift_T + 16) + fmt = texFormatOf[texProp.tex_format] + siz = texBitSizeF3D[texProp.tex_format] + SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) + line = getTileLine(fImage, SL, SH, siz, f3d) + + tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) + tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) + tileSizeCommand.tags |= GfxTag.TileScroll0 if rendertile == 0 else GfxTag.TileScroll1 + tileSizeCommand.fMaterial = fMaterial + if not omitSetTile: + gfxOut.commands.append(tileCommand) + gfxOut.commands.append(tileSizeCommand) + + # hasattr check for FTexRect + if hasattr(fMaterial, "tileSizeCommands"): + fMaterial.tileSizeCommands[rendertile] = tileSizeCommand + + +# palAddr is the address within the second half of tmem (0-255), normally 16*palette num +# palLen is the number of colors +def savePaletteLoad( + gfxOut: GfxList, + fPalette: FImage, + palFormat: str, + palAddr: int, + palLen: int, + loadtile: int, + f3d: F3D, +): + assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 + palFmt = texFormatOf[palFormat] + nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] + + if not f3d._HW_VERSION_1: + gfxOut.commands.extend( + [ + DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), + DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadTLUTCmd(loadtile, palLen - 1), + ] + ) + else: + gfxOut.commands.extend( + [ + _DPLoadTextureBlock( + fPalette, + 256 + palAddr, + palFmt, + "G_IM_SIZ_16b", + 4 * palLen, + 1, + 0, + nocm, + nocm, + 0, + 0, + 0, + 0, + ) + ] + ) + + +# Functions for converting and writing texture and palette data + + +def extractConvertCIPixel(image, pixels, i, j, palFormat): + color = [1, 1, 1, 1] + for field in range(image.channels): + color[field] = pixels[(j * image.size[0] + i) * image.channels + field] + if palFormat == "RGBA16": + pixelColor = getRGBA16Tuple(color) + elif palFormat == "IA16": + pixelColor = getIA16Tuple(color) + else: + raise PluginError("Internal error, palette format is " + palFormat) + return pixelColor + + +def getColorsUsedInImage(image, palFormat): + palette = [] + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + if pixelColor not in palette: + palette.append(pixelColor) + return palette + + +def mergePalettes(pal0, pal1): + palette = [c for c in pal0] + for c in pal1: + if c not in palette: + palette.append(c) + return palette + + +def getColorIndicesOfTexture(image, palette, palFormat): + texture = [] + # N64 is -Y, Blender is +Y + pixels = image.pixels[:] + for j in reversed(range(image.size[1])): + for i in range(image.size[0]): + pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) + if pixelColor not in palette: + raise PluginError(f"Bug: {image.name} palette len {len(palette)} missing CI") + texture.append(palette.index(pixelColor)) + return texture + + +def compactNibbleArray(texture, width, height): + nibbleData = bytearray(0) + dataSize = int(width * height / 2) + + nibbleData = [((texture[i * 2] & 0xF) << 4) | (texture[i * 2 + 1] & 0xF) for i in range(dataSize)] + + if (width * height) % 2 == 1: + nibbleData.append((texture[-1] & 0xF) << 4) + + return bytearray(nibbleData) + + +def writePaletteData(fPalette: FImage, palette: list[int]): + if fPalette.converted: + return + for color in palette: + fPalette.data.extend(color.to_bytes(2, "big")) + fPalette.converted = True + + +def writeCITextureData( + image: bpy.types.Image, + fImage: FImage, + palette: list[int], + palFmt: str, + texFmt: str, +): + if fImage.converted: + return + + texture = getColorIndicesOfTexture(image, palette, palFmt) + + if texFmt == "CI4": + fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) + else: + fImage.data = bytearray(texture) + fImage.converted = True + + +def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): + if fImage.converted: + return + fmt = texFormatOf[texFmt] + bitSize = texBitSizeF3D[texFmt] + + pixels = image.pixels[:] + if fmt == "G_IM_FMT_RGBA": + if bitSize == "G_IM_SIZ_16b": + fImage.data = bytearray( + [ + byteVal + for doubleByte in [ + ( + ( + ((int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3) + | ( + (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) + >> 2 + ) + ), + ( + ((int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6) + | ( + (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) + << 1 + ) + | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) + ), + ) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + for byteVal in doubleByte + ] + ) + elif bitSize == "G_IM_SIZ_32b": + fImage.data = bytearray( + [ + int(round(pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + for field in range(image.channels) + ] + ) + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + + elif fmt == "G_IM_FMT_YUV": + raise PluginError("YUV not yet implemented.") + if bitSize == "G_IM_SIZ_16b": + pass + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + + elif fmt == "G_IM_FMT_CI": + raise PluginError("CI not yet implemented.") + + elif fmt == "G_IM_FMT_IA": + if bitSize == "G_IM_SIZ_4b": + fImage.data = bytearray( + [ + ( + ( + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] + ) + * 0x7 + ) + ) + & 0x7 + ) + << 1 + ) + | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_8b": + fImage.data = bytearray( + [ + ( + ( + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] + ) + * 0xF + ) + ) + & 0xF + ) + << 4 + ) + | (int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_16b": + fImage.data = bytearray( + [ + byteVal + for doubleByte in [ + ( + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) + * image.channels : (j * image.size[0] + i) + * image.channels + + 3 + ] + ) + * 0xFF + ) + ) + & 0xFF, + int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF, + ) + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + for byteVal in doubleByte + ] + ) + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + elif fmt == "G_IM_FMT_I": + if bitSize == "G_IM_SIZ_4b": + fImage.data = bytearray( + [ + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels + + 3 + ] + ) + * 0xF + ) + ) + & 0xF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + elif bitSize == "G_IM_SIZ_8b": + fImage.data = bytearray( + [ + int( + round( + colorToLuminance( + pixels[ + (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels + + 3 + ] + ) + * 0xFF + ) + ) + & 0xFF + for j in reversed(range(image.size[1])) + for i in range(image.size[0]) + ] + ) + else: + raise PluginError("Invalid combo: " + fmt + ", " + bitSize) + else: + raise PluginError("Invalid image format " + fmt) + + # We stored 4bit values in byte arrays, now to convert + if bitSize == "G_IM_SIZ_4b": + fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) + + fImage.converted = True diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 41a4f111d..3329db9a6 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1,24 +1,14 @@ from typing import Union import functools -import bpy, bmesh, mathutils, os, re, copy, math -from math import pi, ceil -from io import BytesIO +import bpy, mathutils, os, re, copy, math +from math import ceil from bpy.utils import register_class, unregister_class from .f3d_enums import * from .f3d_constants import * -from .f3d_material import ( - all_combiner_uses, - getMaterialScrollDimensions, - getTmemWordUsage, - getTmemMax, - texBitSizeF3D, - texFormatOf, - TextureProperty, - F3DMaterialProperty, -) +from .f3d_material import all_combiner_uses, getMaterialScrollDimensions +from .f3d_texture_writer import MultitexManager from .f3d_gbi import * -from .f3d_gbi import _DPLoadTextureBlock from ..utility import * @@ -219,237 +209,6 @@ def findUVBounds(polygon, uv_data): return minUV, maxUV -class TileLoad: - def __init__(self, material, fMaterial, texDimensions): - self.sl = self.tl = 1000000 # above any actual value - self.sh = self.th = -1 # below any actual value - - self.texFormat = fMaterial.largeTexFmt - self.is4bit = texBitSizeInt[self.texFormat] == 4 - self.tmemWordsAvail = fMaterial.largeTexWords - self.texDimensions = texDimensions - self.materialName = material.name - self.isPointSampled = isTexturePointSampled(material) - self.largeEdges = material.f3d_mat.large_edges - - self.faces = [] - self.offsets = [] - - def getLow(self, value, field): - value = int(math.floor(value)) - if self.largeEdges == "Clamp": - value = min(max(value, 0), self.texDimensions[field] - 1) - if self.is4bit and field == 0: - # Must start on an even texel (round down) - value &= ~1 - return value - - def getHigh(self, value, field): - value = int(math.ceil(value)) - (1 if self.isPointSampled else 0) - if self.largeEdges == "Clamp": - value = min(max(value, 0), self.texDimensions[field] - 1) - if self.is4bit and field == 0: - # Must end on an odd texel (round up) - value |= 1 - return value - - def fixRegion(self, sl, sh, tl, th): - assert sl <= sh and tl <= th - soffset = int(math.floor(sl / self.texDimensions[0])) * self.texDimensions[0] - toffset = int(math.floor(tl / self.texDimensions[1])) * self.texDimensions[1] - sl -= soffset - sh -= soffset - tl -= toffset - th -= toffset - assert 0 <= sl < self.texDimensions[0] and 0 <= tl < self.texDimensions[1] - ret = True - if sh >= 1024 or th >= 1024: - ret = False - if sh >= self.texDimensions[0]: - # Load wraps in S. Load must start a multiple of a TMEM word from - # the end of the texture, in order for the second load (beginning of - # image) to start at a whole word. - texelsPerWord = 64 // texBitSizeInt[self.texFormat] - if texelsPerWord > self.texDimensions[0]: - raise PluginError( - f"In large texture material {self.materialName}:" - + f" large texture must be at least {texelsPerWord} wide." - ) - sl -= self.texDimensions[0] - sl = int(math.floor(sl / texelsPerWord)) * texelsPerWord - sl += self.texDimensions[0] - if th >= self.texDimensions[1]: - # Load wraps in T. Load must start a multiple of 2 texture rows from - # the end of the texture, in order for the second load to have the - # same odd/even row parity as the first (because texels are - # interleaved in TMEM every other row). - tl -= self.texDimensions[1] - tl = int(math.floor(tl / 2.0)) * 2 - tl += self.texDimensions[1] - tmemUsage = getTmemWordUsage(self.texFormat, sh - sl + 1, th - tl + 1) - if tmemUsage > self.tmemWordsAvail: - ret = False - return ret, sl, sh, tl, th, soffset, toffset - - def initWithFace(self, obj, face): - uv_data = obj.data.uv_layers["UVMap"].data - faceUVs = [UVtoSTLarge(obj, loopIndex, uv_data, self.texDimensions) for loopIndex in face.loops] - if len(faceUVs) == 0: - return True - - for point in faceUVs: - self.sl = min(self.sl, self.getLow(point[0], 0)) - self.sh = max(self.sh, self.getHigh(point[0], 0)) - self.tl = min(self.tl, self.getLow(point[1], 1)) - self.th = max(self.th, self.getHigh(point[1], 1)) - - ret, self.sl, self.sh, self.tl, self.th, soffset, toffset = self.fixRegion(self.sl, self.sh, self.tl, self.th) - if not ret: - if self.sh >= 1024 or self.th >= 1024: - raise PluginError( - f"Large texture material {self.materialName} has a face that needs" - + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" - + f" (image dims are {self.texDimensions}), but image space" - + f" only goes up to 1024 so this cannot be represented." - ) - else: - raise PluginError( - f"Large texture material {self.materialName} has a face that needs" - + f" to cover texels {self.sl}-{self.sh} x {self.tl}-{self.th}" - + f" ({self.sh-self.sl+1} x {self.th-self.tl+1} texels) " - + f"in format {self.texFormat}, which can't fit in TMEM." - ) - self.faces.append(face) - self.offsets.append((soffset, toffset)) - - def trySubsume(self, other): - # Could do fancier logic checking across borders, for example if we have - # one loading 60-68 (size 64) and another 0-8, that could be merged to - # one load 60-72. But this is likely to be uncommon and won't be generated - # by the operator. - new_sl = min(self.sl, other.sl) - new_sh = max(self.sh, other.sh) - new_tl = min(self.tl, other.tl) - new_th = max(self.th, other.th) - ret, new_sl, new_sh, new_tl, new_th, soffset, toffset = self.fixRegion(new_sl, new_sh, new_tl, new_th) - if not ret: - return False - self.sl, self.sh, self.tl, self.th = new_sl, new_sh, new_tl, new_th - self.faces.extend(other.faces) - self.offsets.extend(other.offsets) - return True - - -def maybeSaveSingleLargeTextureSetup( - i: int, - fMaterial: FMaterial, - fModel: FModel, - fImage: FImage, - gfxOut: GfxList, - texProp: TextureProperty, - texDimensions: tuple[int, int], - tileSettings: TileLoad, - curImgSet: Union[None, int], - curTileLines: list[int], -): - if fMaterial.isTexLarge[i]: - wrapS = tileSettings.sh >= texDimensions[0] - wrapT = tileSettings.th >= texDimensions[1] - assert 0 <= tileSettings.sl < texDimensions[0] - assert 0 <= tileSettings.tl < texDimensions[1] - siz = texBitSizeF3D[texProp.tex_format] - line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) - tmem = fMaterial.largeTexAddr[i] - # print( - # f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " - # + f"tmem {tmem} line {line}" - # ) - if wrapS or wrapT: - fmt = texFormatOf[texProp.tex_format] - texelsPerWord = 64 // texBitSizeInt[texProp.tex_format] - wid = texDimensions[0] - is4bit = siz == "G_IM_SIZ_4b" - if is4bit: - siz = "G_IM_SIZ_8b" - wid >>= 1 - assert (tileSettings.sl & 1) == 0 - assert (tileSettings.sh & 1) == 1 - # TL, TH is always * 4 because tile values are 10.2 fixed. - # SL, SH is * 2 for 4 bit and * 4 otherwise, because actually loading - # 8 bit pairs of texels. Also written using f3d.G_TEXTURE_IMAGE_FRAC. - sm = 2 if is4bit else 4 - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] - if curImgSet != i: - gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) - - def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): - if line != curTileLines[tidxBase]: - gfxOut.commands.append(DPSetTile(fmt, siz, line, tmemBase, tidxBase, 0, nocm, 0, 0, nocm, 0, 0)) - curTileLines[tidxBase] = line - if wrapS: - # Break up at the wrap boundary into two tile loads. - # The first load must occupy a whole number of lines. - assert (texDimensions[0] - tileSettings.sl) % texelsPerWord == 0 - sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerWord - # print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") - gfxOut.commands.append( - DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) - ) - gfxOut.commands.append( - DPSetTile(fmt, siz, line, tmemBase + sLineOfs, tidxBase - 1, 0, nocm, 0, 0, nocm, 0, 0) - ) - curTileLines[tidxBase - 1] = -1 - gfxOut.commands.append( - DPLoadTile(tidxBase - 1, 0, TL * 4, (tileSettings.sh - texDimensions[0]) * sm, TH * 4) - ) - else: - gfxOut.commands.append( - DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, tileSettings.sh * sm, TH * 4) - ) - - if wrapT: - # Break up at the wrap boundary into two loads. - # The first load must be even in size (even number of texture rows). - assert (texDimensions[1] - tileSettings.tl) % 2 == 0 - tLineOfs = line * (texDimensions[1] - tileSettings.tl) - # print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") - loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) - loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) - else: - loadOneOrTwoS(tmem, 7, tileSettings.tl, tileSettings.th) - if fMaterial.isTexLarge[i ^ 1]: - # May reuse any of the above tiles for the other large texture. - gfxOut.commands.append(DPTileSync()) - else: - saveTextureLoadOnly( - fImage, - gfxOut, - texProp, - tileSettings, - 7 - i, - tmem, - fModel.f3d, - curImgSet == i, - line == curTileLines[7 - i], - ) - curTileLines[7 - i] = line - curImgSet = i - saveTextureTile( - fImage, - fMaterial, - gfxOut, - texProp, - tileSettings, - i, - tmem, - fMaterial.texPaletteIndex[i], - fModel.f3d, - line == curTileLines[i], - ) - curTileLines[i] = line - return curImgSet - - def saveMeshWithLargeTexturesByFaces( material, faces, @@ -1608,65 +1367,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): ) useDict = all_combiner_uses(f3dMat) - - # Get texture info, needed for othermode; also check some texture props - err0, info0 = getTexInfoFromMat(0, f3dMat) - err1, info1 = getTexInfoFromMat(1, f3dMat) - if err0 is not None: - raise PluginError(f"In {material.name} tex0: {err0}") - if err1 is not None: - raise PluginError(f"In {material.name} tex1: {err1}") - (useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt, imageDims0, tex0Tmem) = info0 - (useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt, imageDims1, tex1Tmem) = info1 - pal0, pal0Len, im0Use, tex0Flipbook = getTexInfoAdvanced( - 0, material, fMaterial, fModel, useTex0, isTex0Ref, isTex0CI, tex0Fmt, pal0Fmt - ) - pal1, pal1Len, im1Use, tex1Flipbook = getTexInfoAdvanced( - 1, material, fMaterial, fModel, useTex1, isTex1Ref, isTex1CI, tex1Fmt, pal1Fmt - ) - - isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) - isPal0Ref = isTex0Ref and tex0Flipbook is None - isPal1Ref = isTex1Ref and tex1Flipbook is None - - if useTex0 and useTex1: - if isTex0CI != isTex1CI: - raise PluginError( - "In material " - + material.name - + ": N64 does not support CI + non-CI texture. " - + "Must be both CI or neither CI." - ) - if ( - isTex0Ref - and isTex1Ref - and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference - and f3dMat.tex0.tex_reference_size != f3dMat.tex1.tex_reference_size - ): - raise PluginError( - "In material " + material.name + ": Two textures with the same reference must have the same size." - ) - if isCI: - if pal0Fmt != pal1Fmt: - raise PluginError( - "In material " - + material.name - + ": Both CI textures must use the same palette format (usually RGBA16)." - ) - if ( - isTex0Ref - and isTex1Ref - and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference - and f3dMat.tex0.pal_reference_size != f3dMat.tex1.pal_reference_size - ): - raise PluginError( - "In material " - + material.name - + ": Two textures with the same palette reference must have the same palette size." - ) - - palFormat = pal0Fmt if useTex0 else pal1Fmt - g_tt = "G_TT_NONE" if not isCI else ("G_TT_" + palFormat) + multitexManager = MultitexManager(material, fMaterial, fModel) # Set othermode if drawLayer is not None: @@ -1680,7 +1381,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) saveOtherModeHDefinition( - fMaterial, f3dMat.rdp_settings, g_tt, defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod + fMaterial, f3dMat.rdp_settings, multitexManager.getTT(), defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod ) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1695,317 +1396,10 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: fMaterial.material.commands.append(SPTexture(s, t, 0, fModel.f3d.G_TX_RENDERTILE, 1)) - # Determine how to arrange / load palette entries into upper half of tmem - tex0PaletteIndex = 0 - tex1PaletteIndex = 0 - loadPal0 = False - loadPal1 = False - pal0Addr = 0 - pal1Addr = 0 - pal0Use = im0Use - pal1Use = im1Use - if isCI: - assert useTex0 or useTex1 - if not useTex1: - loadPal0 = True - elif not useTex0: - loadPal1 = True - elif not convertTextureData: - if tex0Fmt == "CI8" or tex1Fmt == "CI8": - raise PluginError( - "In material " - + material.name - + ": When using export as PNGs mode, can't have multitexture with one or more CI8 textures." - + " Only single CI texture or two CI4 textures." - ) - loadPal0 = loadPal1 = True - tex1PaletteIndex = 1 - pal1Addr = 16 - else: # Two CI textures, normal mode - if tex0Fmt == "CI8" and tex1Fmt == "CI8": - if (pal0 is None) != (pal1 is None): - raise PluginError( - "In material " - + material.name - + ": can't have two CI8 textures where only one is a non-flipbook reference; " - + "no way to assign the palette." - ) - loadPal0 = True - if pal0 is None: - if f3dMat.tex0.pal_reference != f3dMat.tex1.pal_reference: - raise PluginError( - "In material " - + material.name - + ": can't have two CI8 textures with different palette references." - ) - else: - pal0 = mergePalettes(pal0, pal1) - pal0Len = len(pal0) - if pal0Len > 256: - raise PluginError( - "In material " - + material.name - + ": the two CI textures together contain a total of " - + str(pal0Len) - + " colors, which can't fit in a CI8 palette (256)." - ) - # im0Use remains what it was; the CIs in im0 are the same as they - # would be if im0 was alone. But im1 and pal0 depend on both. - im1Use = pal0Use = im0Use + im1Use - elif tex0Fmt != tex1Fmt: # One CI8, one CI4 - ci8Pal, ci4Pal = (pal0, pal1) if tex0Fmt == "CI8" else (pal1, pal0) - ci8PalLen, ci4PalLen = (pal0Len, pal1Len) if tex0Fmt == "CI8" else (pal1Len, pal0Len) - if pal0 is None or pal1 is None: - if ci8PalLen > 256 - 16: - raise PluginError( - "In material " - + material.name - + ": the CI8 texture has over 240 colors, which can't fit together with the CI4 palette." - ) - loadPal0 = loadPal1 = True - if tex0Fmt == "CI8": - tex1PaletteIndex = 15 - pal1Addr = 240 - else: - tex0PaletteIndex = 15 - pal0Addr = 240 - else: - # CI4 indices in palette 0, CI8 indices start from palette 0 - loadPal0 = True - pal0 = mergePalettes(ci4Pal, ci8Pal) - pal0Len = len(pal0) - if pal0Len > 256: - raise PluginError( - "In material " - + material.name - + ": the two CI textures together contain a total of " - + str(pal0Len) - + " colors, which can't fit in a CI8 palette (256)." - + " The CI8 texture must contain up to 240 unique colors," - + " plus the same up to 16 colors used in the CI4 texture." - ) - # The use for the CI4 texture remains what it was; its CIs are the - # same as if it was alone. But both the palette and the CI8 CIs are affected. - pal0Use = im0Use + im1Use - if tex0Fmt == "CI8": - im0Use = pal0Use - else: - im1Use = pal0Use - else: # both CI4 textures - if pal0 is None and pal1 is None and f3dMat.tex0.pal_reference == f3dMat.tex1.pal_reference: - loadPal0 = True - elif pal0 is None or pal1 is None: - loadPal0 = loadPal1 = True - tex1PaletteIndex = 1 - pal1Addr = 16 - else: - loadPal0 = True - tempPal = mergePalettes(pal0, pal1) - tempPalLen = len(tempPal) - assert tempPalLen <= 32 - if tempPalLen <= 16: - # Share palette 0 - pal0 = tempPal - pal0Len = tempPalLen - # im0Use remains what it was; the CIs in im0 are the same as they - # would be if im0 was alone. But im1 and pal0 depend on both. - im1Use = pal0Use = im0Use + im1Use - else: - # Load one palette across 0-1. Put the longer in slot 0 - if pal0Len >= pal1Len: - while len(pal0) < 16: - pal0.append(0) - pal0.extend(pal1) - pal0Len = len(pal0) - tex1PaletteIndex = 1 - else: - while len(pal1) < 16: - pal1.append(0) - pal0 = pal1 + pal0 - pal0Len = len(pal0) - tex0PaletteIndex = 1 - # The up-to-32 entries in pal0 depend on both images. But the - # CIs in both im0 and im1 are the same as if there was no shared palette. - pal0Use = im0Use + im1Use - fMaterial.texPaletteIndex = [tex0PaletteIndex, tex1PaletteIndex] - pal0BaseName = getPaletteName(useTex0, isPal0Ref, f3dMat.tex0, tex0Flipbook) - pal1BaseName = getPaletteName(useTex1, isPal1Ref, f3dMat.tex1, tex1Flipbook) - if isCI and useTex0 and useTex1 and not loadPal1: - pal0BaseName = pal0BaseName + "_x_" + pal1BaseName - pal1 = pal0 - - # Assign TMEM addresses - sameTextures = ( - useTex0 - and useTex1 - and ( - (not isTex0Ref and not isTex1Ref and f3dMat.tex0.tex == f3dMat.tex1.tex) - or (isTex0Ref and isTex1Ref and f3dMat.tex0.tex_reference == f3dMat.tex1.tex_reference) - ) - ) - useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures - tmemSize = 256 if isCI else 512 - doTex0Load = doTex0Tile = doTex1Load = doTex1Tile = True - tex1Addr = None # must be set whenever tex 1 used (and loaded or tiled) - tmemOccupied = texDimensions = None # must be set on all codepaths - if sameTextures: - assert tex0Tmem == tex1Tmem - tmemOccupied = tex0Tmem - doTex1Load = False - tex1Addr = 0 - texDimensions = imageDims0 - fMaterial.largeTexFmt = tex0Fmt - elif not useLargeTextures or tex0Tmem + tex1Tmem <= tmemSize: - tex1Addr = tex0Tmem - tmemOccupied = tex0Tmem + tex1Tmem - if not useTex0 and not useTex1: - texDimensions = [32, 32] - fMaterial.largeTexFmt = "RGBA16" - elif not useTex1 or f3dMat.uv_basis == "TEXEL0": - texDimensions = imageDims0 - fMaterial.largeTexFmt = tex0Fmt - else: - texDimensions = imageDims1 - fMaterial.largeTexFmt = tex1Fmt - else: # useLargeTextures - if useTex0 and useTex1: - tmemOccupied = tmemSize - # TODO: Could change this in the future to do the face tile assigments - # first, to see how large a tile the large texture(s) needed, instead - # of arbitrarily assigning half of TMEM to each of the two textures. - if tex0Tmem <= tmemSize // 2: - # Tex 0 normal, tex 1 large - texDimensions = imageDims1 - fMaterial.largeTexFmt = tex1Fmt - fMaterial.isTexLarge[1] = True - fMaterial.largeTexAddr[1] = tex0Tmem - fMaterial.largeTexWords = tmemSize - tex0Tmem - doTex1Load = doTex1Tile = False - elif tex1Tmem <= tmemSize // 2: - # Tex 0 large, tex 1 normal - texDimensions = imageDims0 - fMaterial.largeTexFmt = tex0Fmt - fMaterial.isTexLarge[0] = True - fMaterial.largeTexAddr[0] = 0 - fMaterial.largeTexWords = tmemSize - tex1Tmem - doTex0Load = doTex0Tile = False - tex1Addr = tmemSize - tex1Tmem - else: - # Both textures large - raise PluginError( - 'Error in "' + material.name + '": Multitexture with two large textures is not currently supported.' - ) - # Limited cases of 2x large textures could be supported in the - # future. However, these cases are either of questionable - # utility or have substantial restrictions. Most cases could be - # premixed into one texture, or would run out of UV space for - # tiling (1024x1024 in the space of whichever texture had - # smaller pixels), or one of the textures could be non-large. - if f3dMat.uv_basis == "TEXEL0": - texDimensions = imageDims0 - fMaterial.largeTexFmt = tex0Fmt - else: - texDimensions = imageDims1 - fMaterial.largeTexFmt = tex1Fmt - fMaterial.isTexLarge[0] = True - fMaterial.isTexLarge[1] = True - fMaterial.largeTexAddr[0] = 0 - fMaterial.largeTexAddr[1] = tmemSize // 2 - fMaterial.largeTexWords = tmemSize // 2 - doTex0Load = doTex0Tile = doTex1Load = doTex1Tile = False - elif useTex0: - texDimensions = imageDims0 - fMaterial.largeTexFmt = tex0Fmt - fMaterial.isTexLarge[0] = True - fMaterial.largeTexAddr[0] = 0 - fMaterial.largeTexWords = tmemSize - doTex0Load = doTex0Tile = False - tmemOccupied = tmemSize - elif useTex1: - tex1Addr = 0 - texDimensions = imageDims1 - fMaterial.largeTexFmt = tex1Fmt - fMaterial.isTexLarge[1] = True - fMaterial.largeTexAddr[1] = 0 - fMaterial.largeTexWords = tmemSize - doTex1Load = doTex1Tile = False - tmemOccupied = tmemSize - if tmemOccupied > tmemSize: - if sameTextures and useLargeTextures: - raise PluginError( - 'Error in "' - + material.name - + '": Using the same texture for Tex0 and Tex1 is not compatible with large textures.' - ) - elif not bpy.context.scene.ignoreTextureRestrictions: - raise PluginError( - 'Error in "' - + material.name - + '": Textures are too big. Max TMEM size is 4k ' - + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." - ) - - # Get texture and palette definitions - fImage0 = fImage1 = fPalette0 = fPalette1 = None - if useTex0: - imageKey0, fImage0 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex0, im0Use, fMaterial.isTexLarge[0] - ) - fMaterial.imageKey[0] = imageKey0 - if loadPal0: - paletteKey0, fPalette0 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex0, isPal0Ref, pal0Use, pal0BaseName, pal0Len) - if useTex1: - imageKey1, fImage1 = saveOrGetTextureDefinition( - fMaterial, fModel, f3dMat.tex1, im1Use, fMaterial.isTexLarge[1] - ) - fMaterial.imageKey[1] = imageKey1 - if loadPal1: - paletteKey1, fPalette1 = saveOrGetPaletteDefinition(fMaterial, fModel, f3dMat.tex1, isPal1Ref, pal1Use, pal1BaseName, pal1Len) - - # Write DL entries to load textures and palettes - loadGfx = fMaterial.material - if loadPal0: - savePaletteLoad(loadGfx, fPalette0, pal0Fmt, pal0Addr, pal0Len, 5, fModel.f3d) - if useTex0 and doTex0Load: - saveTextureLoadOnly(fImage0, loadGfx, f3dMat.tex0, None, 7, 0, fModel.f3d) - if useTex0 and doTex0Tile: - saveTextureTile(fImage0, fMaterial, loadGfx, f3dMat.tex0, None, 0, 0, tex0PaletteIndex, fModel.f3d) - if loadPal1: - savePaletteLoad(loadGfx, fPalette1, pal1Fmt, pal1Addr, pal1Len, 4, fModel.f3d) - if useTex1 and doTex1Load: - saveTextureLoadOnly(fImage1, loadGfx, f3dMat.tex1, None, 6, tex1Addr, fModel.f3d) - if useTex1 and doTex1Tile: - saveTextureTile(fImage1, fMaterial, loadGfx, f3dMat.tex1, None, 1, tex1Addr, tex1PaletteIndex, fModel.f3d) - - # Write texture and palette data, unless exporting textures as PNGs. - if convertTextureData: - if loadPal0 and not isPal0Ref: - writePaletteData(fPalette0, pal0) - if useTex0: - if isTex0Ref: - if isCI: - fModel.writeTexRefCITextures(tex0Flipbook, fMaterial, im0Use, pal0, tex0Fmt, pal0Fmt) - else: - fModel.writeTexRefNonCITextures(tex0Flipbook, tex0Fmt) - else: - if isCI: - writeCITextureData(f3dMat.tex0.tex, fImage0, pal0, pal0Fmt, tex0Fmt) - else: - writeNonCITextureData(f3dMat.tex0.tex, fImage0, tex0Fmt) - if loadPal1 and not isPal1Ref: - writePaletteData(fPalette1, pal1) - if useTex1: - if isTex1Ref: - if isCI: - fModel.writeTexRefCITextures(tex1Flipbook, fMaterial, im1Use, pal1, tex1Fmt, pal1Fmt) - else: - fModel.writeTexRefNonCITextures(tex1Flipbook, tex1Fmt) - else: - if isCI: - writeCITextureData(f3dMat.tex1.tex, fImage1, pal1, pal1Fmt, tex1Fmt) - else: - writeNonCITextureData(f3dMat.tex1.tex, fImage1, tex1Fmt) - + # Write textures + multitexManager.writeAll(material, fMaterial, fModel, convertTextureData) + + # Write colors nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: color = exportColor(f3dMat.prim_color[0:3]) + [scaleToU8(f3dMat.prim_color[3])] @@ -2078,6 +1472,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: fMaterial.revert = None + texDimensions = multitexManager.getTexDimensions() materialKey = ( material, (drawLayer if f3dMat.rdp_settings.set_rendermode else None), @@ -2088,711 +1483,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): return fMaterial, texDimensions -# Functions for texture and palette definitions - - -def getTextureNamesFromBasename( - baseName: str, - texOrPalFormat: str, - parent: Union[FModel, FTexRect], - isPalette: bool -): - suffix = getTextureSuffixFromFormat(texOrPalFormat) - imageName = parent.name + "_" + baseName + "_" - if isPalette: - imageName += "pal_" - imageName += suffix - imageName = checkDuplicateTextureName(parent, toAlnum(imageName)) - filename = baseName + "." + suffix + (".pal" if isPalette else ".inc.c") - return imageName, filename - - -def getImageName(image: bpy.types.Image): - if image is None: - raise PluginError("No image set in material!") - elif image.filepath == "": - return image.name - else: - return getNameFromPath(image.filepath, True) - - -def getTextureNamesFromImage( - image: bpy.types.Image, - texFormat: str, - parent: Union[FModel, FTexRect] -): - return getTextureNamesFromBasename(getImageName(image), texFormat, parent, False) - - -def getTextureNamesFromProp( - texProp: TextureProperty, - parent: Union[FModel, FTexRect] -): - if texProp.use_tex_reference: - raise PluginError("Internal error, invalid use of getTextureNamesFromProp") - return getTextureNamesFromImage(texProp.tex, texProp.tex_format, parent) - - -def getPaletteName( - useTex: bool, - isPalRef: bool, - texProp: TextureProperty, - flipbook: Union[None, "TextureFlipbook"], -): - if not useTex or isPalRef: - return None - if flipbook is not None: - return flipbook.name - return getImageName(texProp.tex) - - -def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): - names = [] - for info, texture in parent.textures.items(): - names.append(texture.name) - while name in names: - name = name + "_copy" - return name - - -def saveOrGetPaletteDefinition( - fMaterial: FMaterial, - parent: Union[FModel, FTexRect], - texProp: TextureProperty, - isPalRef: bool, - images: list[bpy.types.Image], - palBaseName: str, - palLen: int, -) -> tuple[FPaletteKey, FImage]: - - texFmt = texProp.tex_format - palFmt = texProp.ci_format - palFormat = texFormatOf[palFmt] - paletteKey = FPaletteKey(palFmt, images) - - if isPalRef: - fPalette = FImage(texProp.pal_reference, None, None, 1, palLen, None) - return paletteKey, fPalette - - # If palette already loaded, return that data. - fPalette = parent.getTextureAndHandleShared(paletteKey) - if fPalette is not None: - # print(f"Palette already exists") - return paletteKey, fPalette - - paletteName, filename = getTextureNamesFromBasename(palBaseName, palFmt, parent, True) - fPalette = FImage(paletteName, palFormat, "G_IM_SIZ_16b", 1, palLen, filename) - - parent.addTexture(paletteKey, fPalette, fMaterial) - return paletteKey, fPalette - - -def saveOrGetTextureDefinition( - fMaterial: FMaterial, - parent: Union[FModel, FTexRect], - texProp: TextureProperty, - images: list[bpy.types.Image], - isLarge: bool, -) -> tuple[FImageKey, FImage]: - - image = texProp.tex - texFmt = texProp.tex_format - texFormat = texFormatOf[texFmt] - bitSize = texBitSizeF3D[texFmt] - imageKey = getImageKey(texProp, images) - - if texProp.use_tex_reference: - width, height = texProp.tex_reference_size - fImage = FImage(texProp.tex_reference, None, None, width, height, None) - return imageKey, fImage - - # If image already loaded, return that data. - fImage = parent.getTextureAndHandleShared(imageKey) - if fImage is not None: - # print(f"Image already exists") - return imageKey, fImage - - imageName, filename = getTextureNamesFromProp(texProp, parent) - fImage = FImage(imageName, texFormat, bitSize, image.size[0], image.size[1], filename) - fImage.isLargeTexture = isLarge - - parent.addTexture(imageKey, fImage, fMaterial) - return imageKey, fImage - - -def getTexInfoFromMat( - index: int, - f3dMat: F3DMaterialProperty, -): - texProp = getattr(f3dMat, "tex" + str(index)) - - useDict = all_combiner_uses(f3dMat) - if not useDict["Texture " + str(index)]: - return None, (False, False, False, "", "", (0, 0), 0) - - return getTexInfoFromProp(texProp) - - -def getTexInfoFromProp(texProp: TextureProperty): - if not texProp.tex_set: - return None, (False, False, False, "", "", (0, 0), 0) - - tex = texProp.tex - isTexRef = texProp.use_tex_reference - texFormat = texProp.tex_format - isCITexture = texFormat[:2] == "CI" - palFormat = texProp.ci_format if isCITexture else "" - - if tex is not None and (tex.size[0] == 0 or tex.size[1] == 0): - return f"Image {tex.name} has 0 size; may have been deleted/moved.", None - - if not isTexRef: - if tex is None: - return f"No texture is selected.", None - elif len(tex.pixels) == 0: - return f"Image {tex.name} is missing on disk.", None - - if isTexRef: - width, height = texProp.tex_reference_size - else: - width, height = tex.size - - tmemSize = getTmemWordUsage(texFormat, width, height) - - if width > 1024 or height > 1024: - return f"Image size (even large textures) limited to 1024 in each dimension.", None - - if texBitSizeInt[texFormat] == 4 and (width & 1) != 0: - return f"A 4-bit image must have a width which is even.", None - - info = (True, isTexRef, isCITexture, texFormat, palFormat, (width, height), tmemSize) - return None, info - - -def getTexInfoAdvanced( - index: int, - material: bpy.types.Material, - fMaterial: FMaterial, - fModel: FModel, - useTex: bool, - isTexRef: bool, - isCITexture: bool, - texFormat: str, - palFormat: str, -): - if not useTex: - return None, 0, [], None - - f3dMat = material.f3d_mat - texProp = getattr(f3dMat, "tex" + str(index)) - - pal = None - palLen = 0 - if isCITexture: - imUse, flipbook, pal = fModel.processTexRefCITextures(fMaterial, material, index) - if isTexRef: - if flipbook is not None: - palLen = len(pal) - else: - palLen = texProp.pal_reference_size - else: - assert flipbook is None - pal = getColorsUsedInImage(texProp.tex, palFormat) - palLen = len(pal) - if palLen > (16 if texFormat == "CI4" else 256): - raise PluginError( - f"Error in {material.name}: texture {index}" - + (" (all flipbook textures)" if flipbook is not None else "") - + f" uses too many unique colors to fit in format {texFormat}." - ) - else: - imUse, flipbook = fModel.processTexRefNonCITextures(fMaterial, material, index) - - return pal, palLen, imUse, flipbook - - -# Functions for writing texture and palette DLs - - -def getTileSizeSettings(texProp: TextureProperty, tileSettings: Union[None, TileLoad], f3d: F3D): - if tileSettings is not None: - SL = tileSettings.sl - TL = tileSettings.tl - SH = tileSettings.sh - TH = tileSettings.th - else: - SL = texProp.S.low - TL = texProp.T.low - SH = texProp.S.high - TH = texProp.T.high - sl = int(SL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) - tl = int(TL * (2**f3d.G_TEXTURE_IMAGE_FRAC)) - sh = int(SH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) - th = int(TH * (2**f3d.G_TEXTURE_IMAGE_FRAC)) - return SL, TL, SH, TH, sl, tl, sh, th - - -def getTileLine(fImage: FImage, SL: int, SH: int, siz: str, f3d: F3D): - width = int(SH - SL + 1) if fImage.isLargeTexture else int(fImage.width) - if siz == "G_IM_SIZ_4b": - line = (((width + 1) >> 1) + 7) >> 3 - else: - # Note that _LINE_BYTES and _TILE_BYTES variables are the same. - line = int((width * f3d.G_IM_SIZ_VARS[siz + "_LINE_BYTES"]) + 7) >> 3 - return line - - -def canUseLoadBlock(fImage: FImage, tex_format: str, f3d: F3D): - if fImage.isLargeTexture: - return False - width, height = fImage.width, fImage.height - texelsPerWord = 64 // texBitSizeInt[tex_format] - if width % texelsPerWord != 0: - return False - wordsperrow = width // texelsPerWord - dxt = ((1 << f3d.G_TX_DXT_FRAC) + wordsperrow - 1) // wordsperrow - error = (dxt * wordsperrow) - (1 << f3d.G_TX_DXT_FRAC) - assert error >= 0 - if error == 0: - return True - rowsWhenCorruptionHappens = (dxt + error - 1) // error - return height < rowsWhenCorruptionHappens - - -def saveTextureLoadOnly( - fImage: FImage, - gfxOut: GfxList, - texProp: TextureProperty, - tileSettings: Union[None, TileLoad], - loadtile: int, - tmem: int, - f3d: F3D, - omitSetTextureImage=False, - omitSetTile=False, -): - fmt = texFormatOf[texProp.tex_format] - siz = texBitSizeF3D[texProp.tex_format] - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] - SL, TL, SH, TH, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) - - # LoadTile will pad rows to 64 bit word alignment, while - # LoadBlock assumes this is already done. - useLoadBlock = canUseLoadBlock(fImage, texProp.tex_format, f3d) - line = 0 if useLoadBlock else getTileLine(fImage, SL, SH, siz, f3d) - wid = 1 if useLoadBlock else fImage.width - - if siz == "G_IM_SIZ_4b": - if useLoadBlock: - dxs = (((fImage.width) * (fImage.height) + 3) >> 2) - 1 - dxt = f3d.CALC_DXT_4b(fImage.width) - siz = "G_IM_SIZ_16b" - loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) - else: - sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) - siz = "G_IM_SIZ_8b" - wid >>= 1 - loadCommand = DPLoadTile(loadtile, sl2, tl, sh2, th) - else: - if useLoadBlock: - dxs = ( - ((fImage.width) * (fImage.height) + f3d.G_IM_SIZ_VARS[siz + "_INCR"]) - >> f3d.G_IM_SIZ_VARS[siz + "_SHIFT"] - ) - 1 - dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) - siz += "_LOAD_BLOCK" - loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) - else: - loadCommand = DPLoadTile(loadtile, sl, tl, sh, th) - - if not omitSetTextureImage: - gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) - if not omitSetTile: - gfxOut.commands.append(DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) - gfxOut.commands.append(loadCommand) - - -def saveTextureTile( - fImage: FImage, - fMaterial: FMaterial, - gfxOut: GfxList, - texProp: TextureProperty, - tileSettings, - rendertile: int, - tmem: int, - pal: int, - f3d: F3D, - omitSetTile=False, -): - if tileSettings is not None: - clamp_S = True - clamp_T = True - mirror_S = False - mirror_T = False - mask_S = 0 - mask_T = 0 - shift_S = 0 - shift_T = 0 - else: - clamp_S = texProp.S.clamp - clamp_T = texProp.T.clamp - mirror_S = texProp.S.mirror - mirror_T = texProp.T.mirror - mask_S = texProp.S.mask - mask_T = texProp.T.mask - shift_S = texProp.S.shift - shift_T = texProp.T.shift - cms = [("G_TX_CLAMP" if clamp_S else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_S else "G_TX_NOMIRROR")] - cmt = [("G_TX_CLAMP" if clamp_T else "G_TX_WRAP"), ("G_TX_MIRROR" if mirror_T else "G_TX_NOMIRROR")] - masks = mask_S - maskt = mask_T - shifts = shift_S if shift_S >= 0 else (shift_S + 16) - shiftt = shift_T if shift_T >= 0 else (shift_T + 16) - fmt = texFormatOf[texProp.tex_format] - siz = texBitSizeF3D[texProp.tex_format] - SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) - line = getTileLine(fImage, SL, SH, siz, f3d) - - tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) - tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) - tileSizeCommand.tags |= GfxTag.TileScroll0 if rendertile == 0 else GfxTag.TileScroll1 - tileSizeCommand.fMaterial = fMaterial - if not omitSetTile: - gfxOut.commands.append(tileCommand) - gfxOut.commands.append(tileSizeCommand) - - # hasattr check for FTexRect - if hasattr(fMaterial, "tileSizeCommands"): - fMaterial.tileSizeCommands[rendertile] = tileSizeCommand - - -# palAddr is the address within the second half of tmem (0-255), normally 16*palette num -# palLen is the number of colors -def savePaletteLoad( - gfxOut: GfxList, - fPalette: FImage, - palFormat: str, - palAddr: int, - palLen: int, - loadtile: int, - f3d: F3D, -): - assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 - palFmt = texFormatOf[palFormat] - nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] - - if not f3d._HW_VERSION_1: - gfxOut.commands.extend( - [ - DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), - DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadTLUTCmd(loadtile, palLen - 1), - ] - ) - else: - gfxOut.commands.extend( - [ - _DPLoadTextureBlock( - fPalette, - 256 + palAddr, - palFmt, - "G_IM_SIZ_16b", - 4 * palLen, - 1, - 0, - nocm, - nocm, - 0, - 0, - 0, - 0, - ) - ] - ) - - -# Functions for converting and writing texture and palette data - - -def extractConvertCIPixel(image, pixels, i, j, palFormat): - color = [1, 1, 1, 1] - for field in range(image.channels): - color[field] = pixels[(j * image.size[0] + i) * image.channels + field] - if palFormat == "RGBA16": - pixelColor = getRGBA16Tuple(color) - elif palFormat == "IA16": - pixelColor = getIA16Tuple(color) - else: - raise PluginError("Internal error, palette format is " + palFormat) - return pixelColor - - -def getColorsUsedInImage(image, palFormat): - palette = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - if pixelColor not in palette: - palette.append(pixelColor) - return palette - - -def mergePalettes(pal0, pal1): - palette = [c for c in pal0] - for c in pal1: - if c not in palette: - palette.append(c) - return palette - - -def getColorIndicesOfTexture(image, palette, palFormat): - texture = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - if pixelColor not in palette: - raise PluginError(f"Bug: {image.name} palette len {len(palette)} missing CI") - texture.append(palette.index(pixelColor)) - return texture - - -def compactNibbleArray(texture, width, height): - nibbleData = bytearray(0) - dataSize = int(width * height / 2) - - nibbleData = [((texture[i * 2] & 0xF) << 4) | (texture[i * 2 + 1] & 0xF) for i in range(dataSize)] - - if (width * height) % 2 == 1: - nibbleData.append((texture[-1] & 0xF) << 4) - - return bytearray(nibbleData) - - -def writePaletteData(fPalette: FImage, palette: list[int]): - if fPalette.converted: - return - for color in palette: - fPalette.data.extend(color.to_bytes(2, "big")) - fPalette.converted = True - - -def writeCITextureData( - image: bpy.types.Image, - fImage: FImage, - palette: list[int], - palFmt: str, - texFmt: str, -): - if fImage.converted: - return - - texture = getColorIndicesOfTexture(image, palette, palFmt) - - if texFmt == "CI4": - fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) - else: - fImage.data = bytearray(texture) - fImage.converted = True - - -def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): - if fImage.converted: - return - fmt = texFormatOf[texFmt] - bitSize = texBitSizeF3D[texFmt] - - pixels = image.pixels[:] - if fmt == "G_IM_FMT_RGBA": - if bitSize == "G_IM_SIZ_16b": - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ - ( - ( - ((int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) - >> 2 - ) - ), - ( - ((int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) - << 1 - ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - ), - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) - elif bitSize == "G_IM_SIZ_32b": - fImage.data = bytearray( - [ - int(round(pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - for field in range(image.channels) - ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - - elif fmt == "G_IM_FMT_YUV": - raise PluginError("YUV not yet implemented.") - if bitSize == "G_IM_SIZ_16b": - pass - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - - elif fmt == "G_IM_FMT_CI": - raise PluginError("CI not yet implemented.") - - elif fmt == "G_IM_FMT_IA": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ - ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0x7 - ) - ) - & 0x7 - ) - << 1 - ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ - ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xF - ) - ) - & 0xF - ) - << 4 - ) - | (int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_16b": - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xFF - ) - ) - & 0xFF, - int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF, - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - elif fmt == "G_IM_FMT_I": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels - + 3 - ] - ) - * 0xF - ) - ) - & 0xF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels - + 3 - ] - ) - * 0xFF - ) - ) - & 0xFF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - else: - raise PluginError("Invalid combo: " + fmt + ", " + bitSize) - else: - raise PluginError("Invalid image format " + fmt) - - # We stored 4bit values in byte arrays, now to convert - if bitSize == "G_IM_SIZ_4b": - fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) - - fImage.converted = True - - def saveLightsDefinition(fModel, fMaterial, material, lightsName): lights = fModel.getLightAndHandleShared(lightsName) if lights is not None: diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index b45b0f0ce..f50a5b80c 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -4,10 +4,10 @@ from ..utility import * from .f3d_enums import texBitSizeInt from .f3d_material import getTmemWordUsage -from .f3d_writer import getTexInfoFromMat +from .f3d_writer import TexInfo -def getTexInfoForLarge(material): +def getLargeTextureInfo(material): f3dMat = material.f3d_mat if f3dMat is None: return "This is not a Fast3D material.", None @@ -17,38 +17,35 @@ def getTexInfoForLarge(material): if f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_AVERAGE": return 'Texture filter "Average" not supported.', None bilinear = f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_BILERP" - err0, info0 = getTexInfoFromMat(0, f3dMat) - err1, info1 = getTexInfoFromMat(1, f3dMat) - if err0 is not None: - return err0, None - if err1 is not None: - return err1, None - (useTex0, isTex0Ref, isTex0CI, tex0Fmt, _, imageDims0, tex0Tmem) = info0 - (useTex1, isTex1Ref, isTex1CI, tex1Fmt, _, imageDims1, tex1Tmem) = info1 - isCI = (useTex0 and isTex0CI) or (useTex1 and isTex1CI) + ti0, ti1 = TexInfo(), TexInfo() + if not ti0.fromMat(0, f3dMat): + return ti0.errorMsg, None + if not ti1.fromMat(1, f3dMat): + return ti1.errorMsg, None + isCI = ti0.isTexCI or ti1.isTexCI tmemSize = 256 if isCI else 512 - if not useTex0 and not useTex1: + if not ti0.useTex and not ti1.useTex: return "Material does not use textures.", None - if tex0Tmem + tex1Tmem <= tmemSize: + if ti0.tmemSize + ti1.tmemSize <= tmemSize: return "Texture(s) fit in TMEM; not large.", None - if useTex0 and useTex1: - if tex0Tmem <= tmemSize // 2: - largeDims = imageDims1 - largeFmt = tex1Fmt - largeWords = tmemSize - tex0Tmem - elif tex1Tmem <= tmemSize // 2: - largeDims = imageDims0 - largeFmt = tex0Fmt - largeWords = tmemSize - tex1Tmem + if ti0.useTex and ti1.useTex: + if ti0.tmemSize <= tmemSize // 2: + largeDims = ti1.imageDims + largeFmt = ti1.texFormat + largeWords = tmemSize - ti0.tmemSize + elif ti1.tmemSize <= tmemSize // 2: + largeDims = ti0.imageDims + largeFmt = ti0.texFormat + largeWords = tmemSize - ti1.tmemSize else: return "Two large textures not supported.", None - elif useTex0: - largeDims = imageDims0 - largeFmt = tex0Fmt + elif ti0.useTex: + largeDims = ti0.imageDims + largeFmt = ti0.texFormat largeWords = tmemSize else: - largeDims = imageDims1 - largeFmt = tex1Fmt + largeDims = ti1.imageDims + largeFmt = ti1.texFormat largeWords = tmemSize return None, (largeDims, largeFmt, largeWords, largeEdges, bilinear) @@ -111,7 +108,7 @@ def ui_oplargetexture(layout, context): if prop.mat is None: layout.row().label(text="Please select a material.") return - err, info = getTexInfoForLarge(prop.mat) + err, info = getLargeTextureInfo(prop.mat) if err is not None: layout.row().label(icon="ERROR", text=err) return @@ -134,7 +131,7 @@ def ui_oplargetexture(layout, context): def createLargeTextureMeshInternal(bm, prop): # Parameters setup - err, info = getTexInfoForLarge(prop.mat) + err, info = getLargeTextureInfo(prop.mat) if err is not None: raise PluginError(err) (largeDims, largeFmt, largeWords, largeEdges, bilinear) = info diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 5cef8f40e..065305468 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -5,19 +5,14 @@ from ..utility import PluginError, CData, hexOrDecInt, getNameFromPath, getTextureSuffixFromFormat, toAlnum from ..f3d.flipbook import TextureFlipbook, FlipbookProperty, usesFlipbook, ootFlipbookReferenceIsValid -from ..f3d.f3d_writer import ( - VertexGroupInfo, - TriangleConverterInfo, - DPLoadTLUTCmd, - DPSetTextureLUT, - DPSetTile, +from ..f3d.f3d_writer import VertexGroupInfo, TriangleConverterInfo +from ..f3d.f3d_texture_writer import ( getColorsUsedInImage, mergePalettes, writeCITextureData, writeNonCITextureData, getTextureNamesFromImage, ) - from ..f3d.f3d_gbi import ( FModel, FMaterial, @@ -32,6 +27,7 @@ SPMatrix, GfxFormatter, MTX_SIZE, + DPSetTile, ) @@ -201,7 +197,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate flipbook.textureNames.append(fImage_temp.name) flipbook.images.append((flipbookTexture.image, fImage_temp)) - # print(f"Palette length: {len(pal)}") # Checked in getAndCheckTexInfo + # print(f"Palette length: {len(pal)}") # Checked in moreSetupFromModel return allImages, flipbook, pal def writeTexRefCITextures( diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 2e6f70bb2..c627b4ca2 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -4,14 +4,8 @@ from mathutils import Matrix, Vector from bpy.utils import register_class, unregister_class from ..panels import SM64_Panel -from ..f3d.f3d_writer import ( - getTexInfoFromProp, - saveOrGetTextureDefinition, - saveTextureLoadOnly, - saveTextureTile, - writeNonCITextureData, - exportF3DCommon, -) +from ..f3d.f3d_writer import exportF3DCommon +from ..f3d.f3d_texture_writer import TexInfo from ..f3d.f3d_material import TextureProperty, tmemUsageUI, all_combiner_uses, ui_procAnim from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup from .sm64_utility import starSelectWarning @@ -307,20 +301,19 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): drawEndCommands = GfxList("temp", GfxListTag.Draw, DLFormat.Dynamic) - useTex, isTexRef, isTexCI, texFmt, _, imageDims, texTmem = getTexInfoFromProp(texProp) - if not useTex: - raise PluginError("In " + name + ": texture disabled.") - if isTexCI: - raise PluginError("In " + name + ": CI textures not compatible with exportTexRectCommon (b/c copy mode).") - if texTmem > 512: - raise PluginError("In " + name + ": texture is too big (> 4 KiB).") - if texFmt != "RGBA16": - raise PluginError("In " + name + ": texture format must be RGBA16 (b/c copy mode).") - imageKey, fImage = saveOrGetTextureDefinition(fMaterial, fTexRect, texProp, [tex], texName, False) - saveTextureLoadOnly(fImage, fTexRect.draw, texProp, None, 7, 0, fTexRect.f3d) - saveTextureTile(fImage, fMaterial, fTexRect.draw, texProp, None, 0, 0, 0, fTexRect.f3d) - if convertTextureData: - writeNonCITextureData(tex, fImage, texFmt) + ti = TexInfo() + if not ti.fromProp(texProp, 0): + raise PluginError(f"In {name}: {texProp.errorMsg}.") + if not ti.useTex: + raise PluginError(f"In {name}: texture disabled.") + if ti.isTexCI: + raise PluginError(f"In {name}: CI textures not compatible with exportTexRectCommon (b/c copy mode).") + if ti.tmemSize > 512: + raise PluginError(f"In {name}: texture is too big (> 4 KiB).") + if ti.texFormat != "RGBA16": + raise PluginError(f"In {name}: texture format must be RGBA16 (b/c copy mode).") + ti.imUse = [tex] + ti.writeAll(fTexRect.draw, fMaterial, fTexRect, convertTextureData) fTexRect.draw.commands.append( SPScisTextureRectangle(0, 0, (texDimensions[0] - 1) << 2, (texDimensions[1] - 1) << 2, 0, 0, 0) From 2311789bd65b1a55ce0fd0c63d8ea959e0a2c520 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 13 Feb 2023 15:50:04 -0800 Subject: [PATCH 30/38] Fixes to refactor --- fast64_internal/f3d/f3d_material.py | 21 +++++++++ fast64_internal/f3d/f3d_texture_writer.py | 30 +++++++++--- fast64_internal/f3d/f3d_writer.py | 46 ++++--------------- fast64_internal/f3d/op_largetexture.py | 2 +- fast64_internal/sm64/sm64_geolayout_writer.py | 7 ++- 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 4f83f9d8b..0945c6560 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -227,6 +227,27 @@ def getTmemMax(texFormat): return 4096 if texFormat[:2] != "CI" else 2048 +# Necessary for UV half pixel offset (see 13.7.5.3) +def isTexturePointSampled(material): + f3dMat = material.f3d_mat + + return f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_POINT" + + +def isLightingDisabled(material): + f3dMat = material.f3d_mat + return not f3dMat.rdp_settings.g_lighting + + +# Necessary as G_SHADE_SMOOTH actually does nothing +def checkIfFlatShaded(material): + if material.mat_ver > 3: + f3dMat = material.f3d_mat + else: + f3dMat = material + return not f3dMat.rdp_settings.g_shade_smooth + + def F3DOrganizeLights(self, context): # Flag to prevent infinite recursion on update callback with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index cc9a3b36e..e421e4bab 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -1,5 +1,5 @@ from typing import Union, Optional -from dataclasses import dataclass +from dataclasses import dataclass, field import bpy from math import ceil, floor @@ -12,6 +12,7 @@ texFormatOf, TextureProperty, F3DMaterialProperty, + isTexturePointSampled, ) from .f3d_gbi import * from .f3d_gbi import _DPLoadTextureBlock @@ -19,6 +20,20 @@ from ..utility import * +def UVtoSTLarge(obj, loopIndex, uv_data, texDimensions): + uv = uv_data[loopIndex].uv.copy() + uv[1] = 1 - uv[1] + loopUV = uv.freeze() + + # Represent the -0.5 texel offset in the UVs themselves in clamping mode + # if desired, rather than here at export + pixelOffset = 0 + return [ + convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset) / 32, + convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset) / 32, + ] + + class TileLoad: def __init__(self, material, fMaterial, texDimensions): self.sl = self.tl = 1000000 # above any actual value @@ -383,17 +398,17 @@ class TexInfo(): errorMsg: str = "" # Parameters from moreSetupFromModel - pal: Optional[TODO] = None + pal: Optional[list[int]] = None palLen: int = 0 - imUse: list[bpy.types.Image] = [] - flipbook: Optional[TODO] = None + imUse: Optional[list[bpy.types.Image]] = None + flipbook: Optional["TextureFlipbook"] = None isPalRef: bool = False # Parameters computed by MultitexManager.writeAll texAddr: int = 0 palAddr: int = 0 palIndex: int = 0 - palUse: list[bpy.types.Image] = [] + palUse: list[bpy.types.Image] = field(default_factory=list) palBaseName: str = "" loadPal: bool = False doTexLoad: bool = True @@ -455,6 +470,7 @@ def fromProp(self, texProp: TextureProperty, index: int) -> bool: return True def moreSetupFromModel( + self, material: bpy.types.Material, fMaterial: FMaterial, fModel: FModel, @@ -502,13 +518,13 @@ def writeAll(self, ): if not self.useTex: return - assert len(self.imUse) > 0 # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] + assert self.imUse is not None # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] # Get definitions imageKey, fImage = saveOrGetTextureDefinition( fMaterial, fModel, self.texProp, self.imUse, fMaterial.isTexLarge[self.indexInMat] ) - fMaterial.imageKey[indexInMat] = imageKey + fMaterial.imageKey[self.indexInMat] = imageKey if self.loadPal: _, fPalette = saveOrGetPaletteDefinition( fMaterial, fModel, self.texProp, self.isPalRef, self.palUse, self.palBaseName, self.palLen diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 3329db9a6..07ed59d72 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -6,8 +6,14 @@ from .f3d_enums import * from .f3d_constants import * -from .f3d_material import all_combiner_uses, getMaterialScrollDimensions -from .f3d_texture_writer import MultitexManager +from .f3d_material import ( + all_combiner_uses, + getMaterialScrollDimensions, + isTexturePointSampled, + isLightingDisabled, + checkIfFlatShaded, +) +from .f3d_texture_writer import MultitexManager, TileLoad, maybeSaveSingleLargeTextureSetup from .f3d_gbi import * from ..utility import * @@ -593,27 +599,6 @@ def saveTriangleStrip(triConverter, faces, faceSTOffsets, mesh, terminateDL): return triConverter.currentGroupIndex -# Necessary for UV half pixel offset (see 13.7.5.3) -def isTexturePointSampled(material): - f3dMat = material.f3d_mat - - return f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_POINT" - - -def isLightingDisabled(material): - f3dMat = material.f3d_mat - return not f3dMat.rdp_settings.g_lighting - - -# Necessary as G_SHADE_SMOOTH actually does nothing -def checkIfFlatShaded(material): - if material.mat_ver > 3: - f3dMat = material.f3d_mat - else: - f3dMat = material - return not f3dMat.rdp_settings.g_shade_smooth - - def saveMeshByFaces( material, faces, @@ -1085,20 +1070,6 @@ def getHighestFaceWeight(faceWeights): """ -def UVtoSTLarge(obj, loopIndex, uv_data, texDimensions): - uv = uv_data[loopIndex].uv.copy() - uv[1] = 1 - uv[1] - loopUV = uv.freeze() - - # Represent the -0.5 texel offset in the UVs themselves in clamping mode - # if desired, rather than here at export - pixelOffset = 0 - return [ - convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset) / 32, - convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset) / 32, - ] - - def convertVertexData( mesh, loopPos, @@ -1270,6 +1241,7 @@ def getTexDimensions(material): def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): + print(f"Writing material {material.name}") if material.mat_ver > 3: f3dMat = material.f3d_mat else: diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index f50a5b80c..c3799cb15 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -4,7 +4,7 @@ from ..utility import * from .f3d_enums import texBitSizeInt from .f3d_material import getTmemWordUsage -from .f3d_writer import TexInfo +from .f3d_texture_writer import TexInfo def getLargeTextureInfo(material): diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index ba7161e45..844ec0d8e 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -58,6 +58,11 @@ geoNodeRotateOrder, ) +from ..f3d.f3d_material import ( + isTexturePointSampled, + isLightingDisabled, +) + from ..f3d.f3d_writer import ( TriangleConverterInfo, LoopConvertInfo, @@ -70,9 +75,7 @@ saveOrGetF3DMaterial, saveMeshWithLargeTexturesByFaces, saveMeshByFaces, - isLightingDisabled, getF3DVert, - isTexturePointSampled, convertVertexData, ) From a801054660cc54dd64be020e31af51d4c3dd6b1d Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 13 Feb 2023 15:56:21 -0800 Subject: [PATCH 31/38] Applied black --- .gitignore | 1 + fast64_internal/f3d/f3d_material.py | 27 +++-- fast64_internal/f3d/f3d_texture_writer.py | 105 +++++++++--------- fast64_internal/f3d/f3d_writer.py | 9 +- fast64_internal/f3d/op_largetexture.py | 56 +++++++--- fast64_internal/oot/oot_model_classes.py | 24 ++-- fast64_internal/sm64/sm64_geolayout_writer.py | 9 +- 7 files changed, 141 insertions(+), 90 deletions(-) diff --git a/.gitignore b/.gitignore index 61ce85402..28693ec7e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ __pycache__/ *.blend1 /.venv fast64_updater/ +.python-version diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 0945c6560..76e304cf4 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -840,7 +840,6 @@ def draw_simple(self, f3dMat, material, layout, context): self.ui_fog(f3dMat, inputCol, False) def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): - layout.row().prop(material, "menu_tab", expand=True) menuTab = material.menu_tab useDict = all_combiner_uses(f3dMat) @@ -1568,7 +1567,7 @@ def toggle_texture_node_muting(material: bpy.types.Material, texIndex: int, isUs def set_texture_nodes_settings( material: bpy.types.Material, texProperty: "TextureProperty", texIndex: int, isUsed: bool -) -> (list[int] | None): +) -> list[int] | None: node_tree = material.node_tree f3dMat: "F3DMaterialProperty" = material.f3d_mat @@ -1815,7 +1814,6 @@ def load_handler(dummy): # detect if this is one your addon's libraries here if "f3d_material_library.blend" in lib_path: - addon_dir = os.path.dirname(os.path.abspath(__file__)) new_lib_path = os.path.join(addon_dir, "f3d_material_library.blend") @@ -2259,9 +2257,12 @@ def update_combiner_connections_and_preset(self, context: bpy.types.Context): def ui_image( - canUseLargeTextures: bool, layout: bpy.types.UILayout, + canUseLargeTextures: bool, + layout: bpy.types.UILayout, material: bpy.types.Material, - textureProp: TextureProperty, name: str, showCheckBox: bool + textureProp: TextureProperty, + name: str, + showCheckBox: bool, ): inputGroup = layout.box().column() @@ -2304,7 +2305,7 @@ def ui_image( width, height = tex.size[0], tex.size[1] else: width = height = 0 - + if canUseLargeTextures: availTmem = 512 if textureProp.tex_format[:2] == "CI": @@ -2334,13 +2335,21 @@ def ui_image( msg = prop_input.box().column() msg.label(text=f"Suggest {textureProp.tex_format} tex be multiple ", icon="INFO") msg.label(text=f"of {texelsPerWord} pixels wide for fast loading.") - warnClampS = not isPowerOf2(width) and not textureProp.S.clamp and (not textureProp.autoprop or textureProp.S.mask != 0) - warnClampT = not isPowerOf2(height) and not textureProp.T.clamp and (not textureProp.autoprop or textureProp.T.mask != 0) + warnClampS = ( + not isPowerOf2(width) + and not textureProp.S.clamp + and (not textureProp.autoprop or textureProp.S.mask != 0) + ) + warnClampT = ( + not isPowerOf2(height) + and not textureProp.T.clamp + and (not textureProp.autoprop or textureProp.T.mask != 0) + ) if warnClampS or warnClampT: msg = prop_input.box().column() msg.label(text=f"Clamping required for non-power-of-2 image", icon="ERROR") msg.label(text=f"dimensions. Enable clamp or set mask to 0.") - + texFieldSettings = prop_input.column() clampSettings = texFieldSettings.row() clampSettings.prop(textureProp.S, "clamp", text="Clamp S") diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index e421e4bab..0fbc0fa83 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -268,12 +268,7 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # Functions for texture and palette definitions -def getTextureNamesFromBasename( - baseName: str, - texOrPalFormat: str, - parent: Union[FModel, FTexRect], - isPalette: bool -): +def getTextureNamesFromBasename(baseName: str, texOrPalFormat: str, parent: Union[FModel, FTexRect], isPalette: bool): suffix = getTextureSuffixFromFormat(texOrPalFormat) imageName = parent.name + "_" + baseName + "_" if isPalette: @@ -293,24 +288,16 @@ def getImageName(image: bpy.types.Image): return getNameFromPath(image.filepath, True) -def getTextureNamesFromImage( - image: bpy.types.Image, - texFormat: str, - parent: Union[FModel, FTexRect] -): +def getTextureNamesFromImage(image: bpy.types.Image, texFormat: str, parent: Union[FModel, FTexRect]): return getTextureNamesFromBasename(getImageName(image), texFormat, parent, False) -def getTextureNamesFromProp( - texProp: TextureProperty, - parent: Union[FModel, FTexRect] -): +def getTextureNamesFromProp(texProp: TextureProperty, parent: Union[FModel, FTexRect]): if texProp.use_tex_reference: raise PluginError("Internal error, invalid use of getTextureNamesFromProp") return getTextureNamesFromImage(texProp.tex, texProp.tex_format, parent) - def checkDuplicateTextureName(parent: Union[FModel, FTexRect], name): names = [] for info, texture in parent.textures.items(): @@ -386,7 +373,7 @@ def saveOrGetTextureDefinition( @dataclass -class TexInfo(): +class TexInfo: # Main parameters useTex: bool = False isTexRef: bool = False @@ -396,14 +383,14 @@ class TexInfo(): imageDims: tuple[int, int] = (0, 0) tmemSize: int = 0 errorMsg: str = "" - + # Parameters from moreSetupFromModel pal: Optional[list[int]] = None palLen: int = 0 imUse: Optional[list[bpy.types.Image]] = None flipbook: Optional["TextureFlipbook"] = None isPalRef: bool = False - + # Parameters computed by MultitexManager.writeAll texAddr: int = 0 palAddr: int = 0 @@ -413,7 +400,7 @@ class TexInfo(): loadPal: bool = False doTexLoad: bool = True doTexTile: bool = True - + # Internal parameters--copies of passed parameters texProp: Optional[TextureProperty] = None indexInMat: int = -1 @@ -431,7 +418,7 @@ def fromProp(self, texProp: TextureProperty, index: int) -> bool: self.texProp = texProp if not texProp.tex_set: return True - + self.useTex = True tex = texProp.tex self.isTexRef = texProp.use_tex_reference @@ -466,7 +453,7 @@ def fromProp(self, texProp: TextureProperty, index: int) -> bool: if texBitSizeInt[self.texFormat] == 4 and (width & 1) != 0: self.errorMsg = f"A 4-bit image must have a width which is even." return False - + return True def moreSetupFromModel( @@ -477,10 +464,9 @@ def moreSetupFromModel( ) -> None: if not self.useTex: return - + if self.isTexCI: - self.imUse, self.flipbook, self.pal = fModel.processTexRefCITextures( - fMaterial, material, self.indexInMat) + self.imUse, self.flipbook, self.pal = fModel.processTexRefCITextures(fMaterial, material, self.indexInMat) if self.isTexRef: if self.flipbook is not None: self.palLen = len(self.pal) @@ -497,9 +483,8 @@ def moreSetupFromModel( + f" uses too many unique colors to fit in format {self.texFormat}." ) else: - self.imUse, self.flipbook = fModel.processTexRefNonCITextures( - fMaterial, material, self.indexInMat) - + self.imUse, self.flipbook = fModel.processTexRefNonCITextures(fMaterial, material, self.indexInMat) + self.isPalRef = self.isTexRef and self.flipbook is None self.palUse = self.imUse @@ -509,8 +494,9 @@ def getPaletteName(self): if self.flipbook is not None: return self.flipbook.name return getImageName(self.texProp.tex) - - def writeAll(self, + + def writeAll( + self, loadGfx: GfxList, fMaterial: FMaterial, fModel: Union[FModel, FTexRect], @@ -518,8 +504,8 @@ def writeAll(self, ): if not self.useTex: return - assert self.imUse is not None # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] - + assert self.imUse is not None # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] + # Get definitions imageKey, fImage = saveOrGetTextureDefinition( fMaterial, fModel, self.texProp, self.imUse, fMaterial.isTexLarge[self.indexInMat] @@ -529,27 +515,28 @@ def writeAll(self, _, fPalette = saveOrGetPaletteDefinition( fMaterial, fModel, self.texProp, self.isPalRef, self.palUse, self.palBaseName, self.palLen ) - + # Write loads loadGfx = fMaterial.material f3d = fModel.f3d if self.loadPal: - savePaletteLoad( - loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) + savePaletteLoad(loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) if self.doTexLoad: - saveTextureLoadOnly( - fImage, loadGfx, self.texProp, None, 7 - self.indexInMat, self.texAddr, f3d) + saveTextureLoadOnly(fImage, loadGfx, self.texProp, None, 7 - self.indexInMat, self.texAddr, f3d) if self.doTexTile: saveTextureTile( - fImage, fMaterial, loadGfx, self.texProp, None, self.indexInMat, self.texAddr, self.palIndex, f3d) - + fImage, fMaterial, loadGfx, self.texProp, None, self.indexInMat, self.texAddr, self.palIndex, f3d + ) + # Write texture data if convertTextureData: if self.loadPal and not self.isPalRef: writePaletteData(fPalette, self.pal) if self.isTexRef: if self.isTexCI: - fModel.writeTexRefCITextures(self.flipbook, fMaterial, self.imUse, self.pal, self.texFormat, self.palFormat) + fModel.writeTexRefCITextures( + self.flipbook, fMaterial, self.imUse, self.pal, self.texFormat, self.palFormat + ) else: fModel.writeTexRefNonCITextures(self.flipbook, self.texFormat) else: @@ -559,8 +546,9 @@ def writeAll(self, writeNonCITextureData(self.texProp.tex, fImage, self.texFormat) -class MultitexManager(): - def __init__(self, +class MultitexManager: + def __init__( + self, material: bpy.types.Material, fMaterial: FMaterial, fModel: FModel, @@ -617,11 +605,8 @@ def __init__(self, def getTT(self) -> str: return "G_TT_NONE" if not self.isCI else ("G_TT_" + self.palFormat) - def writeAll(self, - material: bpy.types.Material, - fMaterial: FMaterial, - fModel: FModel, - convertTextureData: bool + def writeAll( + self, material: bpy.types.Material, fMaterial: FMaterial, fModel: FModel, convertTextureData: bool ) -> None: f3dMat = material.f3d_mat # Determine how to arrange / load palette entries into upper half of tmem @@ -674,8 +659,14 @@ def writeAll(self, # would be if im0 was alone. But im1 and self.ti0.pal depend on both. self.ti1.imUse = self.ti0.palUse = self.ti0.imUse + self.ti1.imUse elif self.ti0.texFormat != self.ti1.texFormat: # One CI8, one CI4 - ci8Pal, ci4Pal = (self.ti0.pal, self.ti1.pal) if self.ti0.texFormat == "CI8" else (self.ti1.pal, self.ti0.pal) - ci8PalLen, ci4PalLen = (self.ti0.palLen, self.ti1.palLen) if self.ti0.texFormat == "CI8" else (self.ti1.palLen, self.ti0.palLen) + ci8Pal, ci4Pal = ( + (self.ti0.pal, self.ti1.pal) if self.ti0.texFormat == "CI8" else (self.ti1.pal, self.ti0.pal) + ) + ci8PalLen, ci4PalLen = ( + (self.ti0.palLen, self.ti1.palLen) + if self.ti0.texFormat == "CI8" + else (self.ti1.palLen, self.ti0.palLen) + ) if self.ti0.pal is None or self.ti1.pal is None: if ci8PalLen > 256 - 16: raise PluginError( @@ -713,7 +704,11 @@ def writeAll(self, else: self.ti1.imUse = self.ti0.palUse else: # both CI4 textures - if self.ti0.pal is None and self.ti1.pal is None and self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference: + if ( + self.ti0.pal is None + and self.ti1.pal is None + and self.ti0.texProp.pal_reference == self.ti1.texProp.pal_reference + ): self.ti0.loadPal = True elif self.ti0.pal is None or self.ti1.pal is None: self.ti0.loadPal = self.ti1.loadPal = True @@ -761,7 +756,11 @@ def writeAll(self, and self.ti1.useTex and ( (not self.ti0.isTexRef and not self.ti1.isTexRef and self.ti0.texProp.tex == self.ti1.texProp.tex) - or (self.ti0.isTexRef and self.ti1.isTexRef and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference) + or ( + self.ti0.isTexRef + and self.ti1.isTexRef + and self.ti0.texProp.tex_reference == self.ti1.texProp.tex_reference + ) ) ) useLargeTextures = material.mat_ver > 3 and f3dMat.use_large_textures @@ -813,7 +812,9 @@ def writeAll(self, else: # Both textures large raise PluginError( - 'Error in "' + material.name + '": Multitexture with two large textures is not currently supported.' + 'Error in "' + + material.name + + '": Multitexture with two large textures is not currently supported.' ) # Limited cases of 2x large textures could be supported in the # future. However, these cases are either of questionable diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 07ed59d72..0babb158f 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1353,7 +1353,12 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): else: saveGeoModeDefinition(fMaterial, f3dMat.rdp_settings, defaults, fModel.matWriteMethod) saveOtherModeHDefinition( - fMaterial, f3dMat.rdp_settings, multitexManager.getTT(), defaults, fModel.f3d._HW_VERSION_1, fModel.matWriteMethod + fMaterial, + f3dMat.rdp_settings, + multitexManager.getTT(), + defaults, + fModel.f3d._HW_VERSION_1, + fModel.matWriteMethod, ) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1370,7 +1375,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): # Write textures multitexManager.writeAll(material, fMaterial, fModel, convertTextureData) - + # Write colors nodes = material.node_tree.nodes if useDict["Primitive"] and f3dMat.set_prim: diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index c3799cb15..dea1e24e0 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -51,11 +51,23 @@ def getLargeTextureInfo(material): enumOpLTBias = [ - ("Square", "Square (~1x1)", "Almost square loads, rounded up to nearest line. For meshes which will be deformed (e.g. circular) if results with Weak are not acceptable"), + ( + "Square", + "Square (~1x1)", + "Almost square loads, rounded up to nearest line. For meshes which will be deformed (e.g. circular) if results with Weak are not acceptable", + ), ("Weak", "Weak (1x1/2x1)", "Square or twice-width loads, depending on format and free memory, e.g. 32x32 or 64x32"), ("Moderate", "Moderate (4x1/2x1)", "Width 4x or 2x height, e.g. 64x16 or 64x32. Good efficiency balance"), - ("Strong", "Strong (4x1/8x1)", "Width 4x or 8x height, e.g. 64x16 or 128x16. More efficient than Moderate if geometry usually viewed roughly straight-on"), - ("Extreme", "Extreme (ortho+point only)", "Maximum width, up to full texture rows. Maximum efficiency if geometry always aligned to camera (orthographic) and point sampled. Inefficient otherwise"), + ( + "Strong", + "Strong (4x1/8x1)", + "Width 4x or 8x height, e.g. 64x16 or 128x16. More efficient than Moderate if geometry usually viewed roughly straight-on", + ), + ( + "Extreme", + "Extreme (ortho+point only)", + "Maximum width, up to full texture rows. Maximum efficiency if geometry always aligned to camera (orthographic) and point sampled. Inefficient otherwise", + ), ] @@ -182,6 +194,7 @@ def createLargeTextureMeshInternal(bm, prop): # Mesh setup bm.clear() uvlayer = bm.loops.layers.uv.new("UVMap") + def addGrid(svals, tvals): ns, nt = len(svals), len(tvals) verts = [] @@ -190,24 +203,31 @@ def addGrid(svals, tvals): verts.append(bm.verts.new((s * prop.scale, 0.0, -t * prop.scale))) bm.verts.index_update() faces = [] - for ti in range(nt-1): - for si in range(ns-1): - faces.append(bm.faces.new(( - verts[ti*ns+(si+1)], - verts[ti*ns+si], - verts[(ti+1)*ns+si], - verts[(ti+1)*ns+(si+1)], - ))) + for ti in range(nt - 1): + for si in range(ns - 1): + faces.append( + bm.faces.new( + ( + verts[ti * ns + (si + 1)], + verts[ti * ns + si], + verts[(ti + 1) * ns + si], + verts[(ti + 1) * ns + (si + 1)], + ) + ) + ) bm.faces.index_update() - for ti in range(nt-1): - for si in range(ns-1): - f = faces[ti*(ns-1)+si] + for ti in range(nt - 1): + for si in range(ns - 1): + f = faces[ti * (ns - 1) + si] + def getUV(ds, dt): - return Vector((svals[si+ds] * uvScale[0], 1.0 - tvals[ti+dt] * uvScale[1])) + return Vector((svals[si + ds] * uvScale[0], 1.0 - tvals[ti + dt] * uvScale[1])) + f.loops[0][uvlayer].uv = getUV(1, 0) f.loops[1][uvlayer].uv = getUV(0, 0) f.loops[2][uvlayer].uv = getUV(0, 1) f.loops[3][uvlayer].uv = getUV(1, 1) + def clampGridDim(dim): vals = [-prop.clamp_border] d = baseTile[dim] @@ -218,6 +238,7 @@ def clampGridDim(dim): if not bilinear or not prop.lose_pixels or d == imHi: vals.append(imHi + prop.clamp_border) return vals + def wrapGridDim(dim): # Could create a new grid for wrap tris at the edges, because their loads # can often be combined due to their smaller sizes. However, this would @@ -260,6 +281,7 @@ def wrapGridDim(dim): d += nextWrapBdry vals.append(d) return vals + func = clampGridDim if largeEdges == "Clamp" else wrapGridDim addGrid(func(0), func(1)) @@ -270,7 +292,7 @@ class CreateLargeTextureMesh(bpy.types.Operator): bl_options = {"REGISTER", "UNDO", "PRESET"} def execute(self, context): - bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_all(action="DESELECT") prop = context.scene.opLargeTextureProperty assert prop.mat is not None name = prop.mat.name + "Mesh" @@ -283,7 +305,7 @@ def execute(self, context): bm.to_mesh(mesh) bm.free() bpy.context.collection.objects.link(obj) - obj.parent_type = 'OBJECT' + obj.parent_type = "OBJECT" for o in context.scene.objects: if o.name.startswith("Room"): obj.parent = o diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index 065305468..f23e51f37 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -125,22 +125,28 @@ def getRenderMode(self, drawLayer): def addFlipbookWithRepeatCheck(self, flipbook: TextureFlipbook): model = self.getFlipbookOwner() + def raiseErr(submsg): raise PluginError( f"There are two flipbooks {subMsg} trying to write to the same texture array " + f"named: {flipbook.name}.\nMake sure that this flipbook name is unique, or " + "that repeated uses of this name use the same textures in the same order/format." ) + for existingFlipbook in model.flipbooks: if existingFlipbook.name == flipbook.name: if len(existingFlipbook.textureNames) != len(flipbook.textureNames): - raiseErr(f"of different lengths ({len(existingFlipbook.textureNames)} " - + f"vs. {len(flipbook.textureNames)})") + raiseErr( + f"of different lengths ({len(existingFlipbook.textureNames)} " + + f"vs. {len(flipbook.textureNames)})" + ) for i in range(len(flipbook.textureNames)): if existingFlipbook.textureNames[i] != flipbook.textureNames[i]: - raiseErr(f"with differing elements (elem {i} = " + raiseErr( + f"with differing elements (elem {i} = " + f"{existingFlipbook.textureNames[i]} vs. " - + f"{flipbook.textureNames[i]})") + + f"{flipbook.textureNames[i]})" + ) model.flipbooks.append(flipbook) def validateImages(self, material: bpy.types.Material, index: int): @@ -153,8 +159,10 @@ def validateImages(self, material: bpy.types.Material, index: int): raise PluginError(f"Flipbook for {material.name} has a texture array item that has not been set.") imSize = (flipbookTexture.image.size[0], flipbookTexture.image.size[1]) if imSize != refSize: - raise PluginError(f"In {material.name}: texture reference size is {refSize}, " - + f"but flipbook image {flipbookTexture.image.filepath} size is {imSize}.") + raise PluginError( + f"In {material.name}: texture reference size is {refSize}, " + + f"but flipbook image {flipbookTexture.image.filepath} size is {imSize}." + ) if flipbookTexture.image not in allImages: allImages.append(flipbookTexture.image) return allImages @@ -178,7 +186,7 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate imageName, filename = getTextureNamesFromImage(flipbookTexture.image, texProp.tex_format, model) if flipbookProp.exportMode == "Individual": imageName = flipbookTexture.name - + # We don't know yet if this already exists, cause we need the full set # of images which contribute to the palette, which we don't get until # writeTexRefCITextures (in case the other texture in multitexture contributes). @@ -256,7 +264,7 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M filename, ) model.addTexture(imageKey, fImage, fMaterial) - + flipbook.textureNames.append(fImage.name) flipbook.images.append((flipbookTexture.image, fImage)) diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 844ec0d8e..4c1c6ee27 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -2471,7 +2471,7 @@ def saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, dr # Scan the displaylist to look for material loads and reverts # Use a while instead of a for to be able to insert into the list during iteration commandIdx = 0 - + while commandIdx < len(meshMatOverride.commands): # Get the command at the current index command = meshMatOverride.commands[commandIdx] @@ -2543,13 +2543,18 @@ def saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, dr # Reverts are only needed if the next command is a different material load if fMaterial.revert is None and fOverrideMat.revert is not None: nextCommand = meshMatOverride.commands[commandIdx + 1] - if isinstance(nextCommand, SPDisplayList) and nextCommand.displayList.tag == GfxListTag.Material and nextCommand.displayList != prevMaterial.material: + if ( + isinstance(nextCommand, SPDisplayList) + and nextCommand.displayList.tag == GfxListTag.Material + and nextCommand.displayList != prevMaterial.material + ): # Insert the new command meshMatOverride.commands.insert(commandIdx + 1, SPDisplayList(fOverrideMat.revert)) commandIdx += 1 # iterate to the next cmd commandIdx += 1 + def findVertIndexInBuffer(loop, buffer, loopDict): i = 0 for material_index, vertData in buffer: From e93d80891cbaffcfe588c204c9227759af8299d3 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Mon, 13 Feb 2023 16:56:06 -0800 Subject: [PATCH 32/38] Made changes for dragorn --- fast64_internal/f3d/f3d_material.py | 2 +- fast64_internal/f3d/f3d_parser.py | 25 +++++++++++-------------- fast64_internal/f3d/op_largetexture.py | 2 +- fast64_internal/sm64/sm64_f3d_writer.py | 8 ++------ fast64_internal/utility.py | 19 +++++++++++++++++++ 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 76e304cf4..90578be77 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -1468,7 +1468,7 @@ def set_texture_settings_node(material: bpy.types.Material): def setAutoProp(fieldProperty, pixelLength): - fieldProperty.mask = math.ceil(math.log(pixelLength, 2) - 0.001) + fieldProperty.mask = log2iRoundUp(pixelLength) fieldProperty.shift = 0 fieldProperty.low = 0 fieldProperty.high = pixelLength diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index 19e5b3702..9ab7da923 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -68,7 +68,6 @@ def getExportRotation(forwardAxisEnum, convertTransformMatrix): def F3DtoBlenderObject(romfile, startAddress, scene, newname, transformMatrix, segmentData, shadeSmooth): - mesh = bpy.data.meshes.new(newname + "-mesh") obj = bpy.data.objects.new(newname, mesh) scene.collection.objects.link(obj) @@ -271,7 +270,6 @@ def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command, segme # Note the divided by 0x0A, which is due to the way BF command stores indices. # Without this the triangles are drawn incorrectly. def interpretDrawTriangle(command, vertexBuffer, faceSeq, vertSeq, uv_layer, deform_layer, groupIndex): - verts = [None, None, None] index0 = int(command[5] / 0x0A) @@ -742,7 +740,6 @@ def getMaterialKey(self, material: bpy.types.Material): return material.f3d_mat.key() def getMaterialIndex(self): - key = self.getMaterialKey(self.materialContext) if key in self.materialDict: material = self.materialDict[key] @@ -1000,17 +997,17 @@ def setTLUTMode(self, flags): mat = self.mat() if not isinstance(flags, int): flags = math_eval(flags, self.f3d) - else: - flags &= 3 << self.f3d.G_MDSFT_TEXTLUT + tlut_mode = flags & (0b11 << self.f3d.G_MDSFT_TEXTLUT) for index in range(2): texProp = getattr(mat, "tex" + str(index)) - if flags == self.f3d.G_TT_IA16: + if tlut_mode == self.f3d.G_TT_IA16: texProp.ci_format = "IA16" - elif flags == self.f3d.G_TT_RGBA16: + elif tlut_mode == self.f3d.G_TT_RGBA16: texProp.ci_format = "RGBA16" else: # self.f3d.G_TT_NONE or the unsupported value of 1 + # Othermode is set to disable palette/CI; make sure the texture format is not CI if texProp.tex_format[:2] == "CI": - texProp.tex_format = "RGBA16" + texProp.tex_format = texProp.tex_format[1:] # Cut off the C, so CI4->I4 and CI8->I8 def setOtherModeFlags(self, command): mat = self.mat() @@ -1028,12 +1025,14 @@ def setFlagsAttrs(self, command, database): for field, fieldData in database.items(): fieldShift = getattr(self.f3d, field) - if fieldShift >= shift and fieldShift < shift + mask: + if shift <= fieldShift < shift + mask: if isinstance(fieldData, list): - value = (flags >> fieldShift) & ((1 << int(ceil(math.log(len(fieldData), 2)))) - 1) + value = (flags >> fieldShift) & (roundUpToPowerOf2(len(fieldData)) - 1) setattr(mat.rdp_settings, field.lower(), fieldData[value]) - else: + elif callable(fieldData): fieldData(flags) + else: + raise PluginError(f"Internal error in setFlagsAttrs, type(fieldData) == {type(fieldData)}") def setOtherModeFlagsH(self, command): otherModeH = { @@ -1812,6 +1811,7 @@ def __init__(self, name, params): # Static DLs only + # limbName = c variable name (for parsing text) # boneName = blender bone name (for assigning vertex groups) # we distinguish these because there is no guarantee of bone order in blender, @@ -1828,7 +1828,6 @@ def parseF3D( f3dContext: F3DContext, callClearMaterial: bool, ): - f3dContext.matrixData[limbName] = transformMatrix f3dContext.setCurrentTransform(limbName) f3dContext.limbToBoneName[limbName] = boneName @@ -1983,7 +1982,6 @@ def CI4toRGBA32(value): def parseTextureData(dlData, textureName, f3dContext, imageFormat, imageSize, width, isLUT, f3d): - matchResult = re.search( r"([A-Za-z0-9\_]+)\s*" + re.escape(textureName) + r"\s*\[\s*[0-9a-fA-Fx]*\s*\]\s*=\s*\{([^\}]*)\s*\}\s*;\s*", dlData, @@ -2189,7 +2187,6 @@ def importMeshC( f3dContext: F3DContext, callClearMaterial: bool = True, ) -> bpy.types.Object: - # Create new skinned mesh mesh = bpy.data.meshes.new(name + "_mesh") obj = bpy.data.objects.new(name + "_mesh", mesh) diff --git a/fast64_internal/f3d/op_largetexture.py b/fast64_internal/f3d/op_largetexture.py index dea1e24e0..290fcf786 100644 --- a/fast64_internal/f3d/op_largetexture.py +++ b/fast64_internal/f3d/op_largetexture.py @@ -161,7 +161,7 @@ def createLargeTextureMeshInternal(bm, prop): tileSWords = int(math.ceil(largeDims[0] / texelsPerWord)) if largeWords // tileSWords < targetRows: # If that doesn't give us enough rows, reduce to next power of 2 - d = 1 << int(math.floor(math.log2(largeDims[0]))) + d = roundDownToPowerOf2(largeDims[0]) tileSWords = d // texelsPerWord while largeWords // tileSWords < targetRows: tileSWords >>= 1 diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index c627b4ca2..99b071077 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -307,11 +307,11 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): if not ti.useTex: raise PluginError(f"In {name}: texture disabled.") if ti.isTexCI: - raise PluginError(f"In {name}: CI textures not compatible with exportTexRectCommon (b/c copy mode).") + raise PluginError(f"In {name}: CI textures not compatible with exportTexRectCommon (because copy mode).") if ti.tmemSize > 512: raise PluginError(f"In {name}: texture is too big (> 4 KiB).") if ti.texFormat != "RGBA16": - raise PluginError(f"In {name}: texture format must be RGBA16 (b/c copy mode).") + raise PluginError(f"In {name}: texture format must be RGBA16 (because copy mode).") ti.imUse = [tex] ti.writeAll(fTexRect.draw, fMaterial, fTexRect, convertTextureData) @@ -470,7 +470,6 @@ def sm64ExportF3DtoC( def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, segmentData, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat) fMesh = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) fModel.freePalettes() @@ -490,7 +489,6 @@ def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, RAMAddr, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat) fMesh = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) segmentData = copy.copy(bank0Segment) @@ -510,7 +508,6 @@ def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType, def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, f3dType, isHWv1, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat) fMesh = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) @@ -674,7 +671,6 @@ def execute(self, context): + hex(startAddress + 0x80000000), ) else: - self.report( {"INFO"}, "Success! DL at (" diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index a11081b9e..b4c133ded 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -54,6 +54,24 @@ def isPowerOf2(n): return (n & (n - 1) == 0) and n != 0 +def log2iRoundDown(n): + assert n > 0 + return int(math.floor(math.log2(n))) + + +def log2iRoundUp(n): + assert n > 0 + return int(math.ceil(math.log2(n))) + + +def roundDownToPowerOf2(n): + return 1 << log2iRoundDown(n) + + +def roundUpToPowerOf2(n): + return 1 << log2iRoundUp(n) + + def getDeclaration(data, name): matchResult = re.search("extern\s*[A-Za-z0-9\_]*\s*" + re.escape(name) + "\s*(\[[^;\]]*\])?;\s*", data, re.DOTALL) return matchResult @@ -1301,6 +1319,7 @@ def convertEulerFloatToShort(value): # Rotation + # Rotation is stored as a short. # Zero rotation starts at Z+ on an XZ plane and goes counterclockwise. # 2**16 - 1 is the last value before looping around again. From a096c1fff9cffcffad58c3bac2dd8fb9facf444e Mon Sep 17 00:00:00 2001 From: scut Date: Sun, 19 Feb 2023 12:29:52 -0500 Subject: [PATCH 33/38] updated instances of use large textures with checking large texture use per tile --- fast64_internal/f3d/f3d_bleed.py | 4 ++-- fast64_internal/oot/oot_f3d_writer.py | 2 +- fast64_internal/sm64/sm64_geolayout_writer.py | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index 765301e1f..e9e69b6e6 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -69,7 +69,7 @@ def bleed_fmesh(self, f3d: F3D, fMesh: FMesh, lastMat: FMaterial, cmd_list: GfxL bleed_gfx_lists = BleedGfxLists() if triGroup.fMaterial: bleed_gfx_lists.bled_mats = self.bleed_mat(triGroup.fMaterial, lastMat, cmd_list, bleed_state) - if not triGroup.fMaterial.largeTexFmt: + if not (triGroup.fMaterial.isTexLarge[0] or triGroup.fMaterial.isTexLarge[1]): bleed_gfx_lists.bled_tex = self.bleed_textures(triGroup.fMaterial, lastMat, cmd_list, bleed_state) lastMat = triGroup.fMaterial # bleed tri group (for large textures) and to remove other unnecessary cmds @@ -144,7 +144,7 @@ def bleed_tri_group( # remove SPEndDisplayList from triGroup while SPEndDisplayList() in triGroup.triList.commands: triGroup.triList.commands.remove(SPEndDisplayList()) - if triGroup.fMaterial.largeTexFmt: + if (triGroup.fMaterial.isTexLarge[0] or triGroup.fMaterial.isTexLarge[1]): triGroup.triList = self.bleed_cmd_list(triGroup.triList, bleed_state) # this is a little less versatile than comparing by last used material diff --git a/fast64_internal/oot/oot_f3d_writer.py b/fast64_internal/oot/oot_f3d_writer.py index 8207f23c1..b36644870 100644 --- a/fast64_internal/oot/oot_f3d_writer.py +++ b/fast64_internal/oot/oot_f3d_writer.py @@ -158,7 +158,7 @@ def ootProcessVertexGroup( material, fModel, meshObj, drawLayerOverride, convertTextureData ) - if fMaterial.useLargeTextures: + if fMaterial.isTexLarge[0] or fMaterial.isTexLarge[1]: currentGroupIndex = saveMeshWithLargeTexturesByFaces( material, faces, diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index bf47458dc..70f0ab813 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -2416,8 +2416,7 @@ def saveModelGivenVertexGroup( material = obj.material_slots[material_index].material checkForF3dMaterialInFaces(obj, material) fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - # there is an error here, this is not part of the mat_bleed PR - if fMaterial.useLargeTextures: + if fMaterial.isTexLarge[0] or fMaterial.isTexLarge[1]: currentGroupIndex = saveMeshWithLargeTexturesByFaces( material, bFaces, @@ -2737,7 +2736,7 @@ def saveSkinnedMeshByMaterial( material = obj.material_slots[material_index].material faces = [skinnedFace.bFace for skinnedFace in skinnedFaceArray] fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - if fMaterial.useLargeTextures: + if fMaterial.isTexLarge[0] or fMaterial.isTexLarge[1]: saveMeshWithLargeTexturesByFaces( material, faces, From c3962ed8e01717bb843227394706763b613fd9a0 Mon Sep 17 00:00:00 2001 From: scut Date: Fri, 17 Mar 2023 13:23:42 -0400 Subject: [PATCH 34/38] re-wrote tile bleed to work with new texture writer, and made all textureDLs go to a single gfx_list instead of separate ones per tile --- fast64_internal/f3d/f3d_bleed.py | 97 ++++++++++++++--------- fast64_internal/f3d/f3d_gbi.py | 17 ++-- fast64_internal/f3d/f3d_texture_writer.py | 6 +- fast64_internal/sm64/sm64_f3d_writer.py | 2 +- 4 files changed, 71 insertions(+), 51 deletions(-) diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index e9e69b6e6..14e6e4891 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -80,42 +80,63 @@ def bleed_fmesh(self, f3d: F3D, fMesh: FMesh, lastMat: FMaterial, cmd_list: GfxL self.on_bleed_end(f3d, lastMat, bleed_gfx_lists, cmd_list, default_render_mode) return lastMat + def build_tmem_dict(self, cmd_list: GfxList): + im_buffer = None + tmem_dict = dict() + tile_dict = {i:0 for i in range(8)} # an assumption that hopefully never needs correction + for cmd in cmd_list.commands: + if type(cmd) == DPSetTextureImage: + im_buffer = cmd + continue + if type(cmd) == DPSetTile: + tile_dict[cmd.tile] = cmd.tmem + if type(cmd) in (DPLoadTLUTCmd, DPLoadTile, DPLoadBlock): + tmem_dict[tile_dict[cmd.tile]] = im_buffer + continue + return tmem_dict + def bleed_textures(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxList, bleed_state: int): if lastMat: - bled_tex = [] # bleed cmds if matching tile has duplicate cmds - for j, (LastTex, TexCmds) in enumerate(zip(lastMat.texture_DLs, curMat.texture_DLs)): - # deep copy breaks on Image objects so I will only copy the levels needed - commands_bled = copy.copy(TexCmds) - commands_bled.commands = copy.copy(TexCmds.commands) # copy the commands also - lastList = LastTex.commands - # eliminate set tex images - set_tex = (c for c in TexCmds.commands if type(c) == DPSetTextureImage) - removed_tex = [c for c in set_tex if c in lastList] # needs to be a list to check "in" multiple times - rm_load = None # flag to elim loads once - for j, cmd in enumerate(TexCmds.commands): - # remove set tex explicitly - if cmd in removed_tex: - commands_bled.commands.remove(cmd) - rm_load = True - continue - if rm_load and type(cmd) in (DPLoadTLUTCmd, DPLoadTile, DPLoadBlock): - commands_bled.commands.remove(cmd) - rm_load = None - continue - # now eval as normal conditionals - iter_cmds = copy.copy(commands_bled.commands) # need extra list to iterate with - for j, cmd in enumerate(iter_cmds): - if self.bleed_individual_cmd(commands_bled, cmd, bleed_state): - if cmd in lastList: - commands_bled.commands[j] = None - # remove Nones from list - while None in commands_bled.commands: - commands_bled.commands.remove(None) - bled_tex.append(commands_bled) + # deep copy breaks on Image objects so I will only copy the levels needed + commands_bled = copy.copy(curMat.texture_DL) + commands_bled.commands = copy.copy(curMat.texture_DL.commands) # copy the commands also + # eliminate set tex images, but only if there is an overlap of the same image at the same tmem location + last_im_loads = self.build_tmem_dict(lastMat.texture_DL) + new_im_loads = self.build_tmem_dict(commands_bled) + removable_images = [] + for tmem, image in new_im_loads.items(): + if tmem in last_im_loads and last_im_loads[tmem] == image: + removable_images.append(image) + # now go through list and cull out loads for the specific cmds + # this will be the set tex image, and the loading cmds + rm_load = False + for j, cmd in enumerate(curMat.texture_DL.commands): + # remove set tex explicitly + if cmd in removable_images: + commands_bled.commands[j] = None + rm_load = True + continue + if rm_load and type(cmd) == DPSetTile: + commands_bled.commands[j] = None + if rm_load and type(cmd) in (DPLoadTLUTCmd, DPLoadTile, DPLoadBlock): + commands_bled.commands[j] = None + rm_load = None + continue + # now eval as normal conditionals + for j, cmd in enumerate(curMat.texture_DL.commands): + if not cmd: + continue # some cmds are None from previous step + if self.bleed_individual_cmd(commands_bled, cmd, bleed_state): + if cmd in lastMat.texture_DL.commands: + commands_bled.commands[j] = None + # remove Nones from list + while None in commands_bled.commands: + commands_bled.commands.remove(None) + bled_tex = commands_bled else: - bled_tex = curMat.texture_DLs - return bled_tex + bled_tex = curMat.texture_DL + return bled_tex.commands def bleed_mat(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxList, bleed_state: int): if lastMat: @@ -136,7 +157,7 @@ def bleed_mat(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxList, bl # remove SPEndDisplayList while SPEndDisplayList() in commands_bled.commands: commands_bled.commands.remove(SPEndDisplayList()) - return commands_bled + return commands_bled.commands def bleed_tri_group( self, f3d: F3D, triGroup: FTriGroup, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList, bleed_state: int @@ -167,10 +188,9 @@ def bleed_cmd_list(self, target_cmd_list: GfxList, bleed_state: int): # Put triGroup bleed gfx in the FMesh.draw object def inline_triGroup(self, f3d: F3D, triGroup: FTriGroup, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList): # add material - cmd_list.commands.extend(bleed_gfx_lists.bled_mats.commands) + cmd_list.commands.extend(bleed_gfx_lists.bled_mats) # add textures - for tile, texGfx in enumerate(bleed_gfx_lists.bled_tex): - cmd_list.commands.extend(texGfx.commands) + cmd_list.commands.extend(bleed_gfx_lists.bled_tex) # add in triangles cmd_list.commands.extend(triGroup.triList.commands) # skinned meshes don't draw tris sometimes, use this opportunity to save a sync @@ -221,7 +241,9 @@ def bleed_individual_cmd(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: in SPBranchLessZraw, SPModifyVertex, SPEndDisplayList, + DPSetTextureImage, DPLoadBlock, + DPLoadTile, DPLoadTLUTCmd, DPFullSync, ]: @@ -238,6 +260,7 @@ def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: in return True def bleed_DPSetTileSize(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): + print(cmd.tags, cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1) return cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1 def bleed_DPSetTile(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): @@ -278,7 +301,7 @@ def bleed_DPLoadSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): @dataclass class BleedGfxLists: bled_mats: GfxList = field(default_factory=list) - bled_tex: list[GfxList] = field(default_factory=list) # list of GfxList + bled_tex: GfxList = field(default_factory=list) reset_cmds: set[GbiMacro] = field(default_factory=set) # set of cmds to reset def __post_init__(self): diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 06e95823f..6f2532f77 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -2309,7 +2309,7 @@ def writeTexRefCITextures( # Called before SPEndDisplayList def onMaterialCommandsBuilt(self, fMaterial, material, drawLayer): fMaterial.material.commands.extend(fMaterial.mat_only_DL.commands) - [fMaterial.material.commands.extend(tile_gfx_list.commands) for tile_gfx_list in fMaterial.texture_DLs] + fMaterial.material.commands.extend(fMaterial.texture_DL.commands) return def getDrawLayerV3(self, obj): @@ -2903,10 +2903,10 @@ def get_f3d_mat_from_version(material: bpy.types.Material): class FMaterial: def __init__(self, name, DLFormat): - self.material = GfxList("mat_" + name, GfxListTag.Material, DLFormat) - self.mat_only_DL = GfxList("mat_only_" + name, GfxListTag.Material, DLFormat) - self.texture_DLs = [GfxList(f"tex_{i}_" + name, GfxListTag.Material, DLFormat.Static) for i in range(2)] - self.revert = GfxList("mat_revert_" + name, GfxListTag.MaterialRevert, DLFormat.Static) + self.material = GfxList(f"mat_{name}", GfxListTag.Material, DLFormat) + self.mat_only_DL = GfxList(f"mat_only_{name}", GfxListTag.Material, DLFormat) + self.texture_DL = GfxList(f"tex_{name}", GfxListTag.Material, DLFormat.Static) + self.revert = GfxList(f"mat_revert_{name}", GfxListTag.MaterialRevert, DLFormat.Static) self.DLFormat = DLFormat self.scrollData = FScrollData() @@ -2924,9 +2924,6 @@ def __init__(self, name, DLFormat): self.imageKey = [None, None] self.texPaletteIndex = [0, 0] - def getTexturesGfxList(self, tile): - return self.texture_DLs[tile] - def getScrollData(self, material, dimensions): self.getScrollDataField(material, 0, 0) self.getScrollDataField(material, 0, 1) @@ -4328,8 +4325,8 @@ class DPSetTextureImage(GbiMacro): siz: str width: int image: FImage - _segptrs = True # calls segmented_to_virtualon name when needed - + _segptrs = True # calls segmented_to_virtual on name when needed + def to_binary(self, f3d, segments): fmt = f3d.G_IM_FMT_VARS[self.fmt] siz = f3d.G_IM_SIZ_VARS[self.siz] diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index bc5659a40..be60cb1d8 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -517,7 +517,7 @@ def writeAll( ) # Write loads - loadGfx = fMaterial.mat_only_DL + # loadGfx = fMaterial.mat_only_DL # I don't know what the point of this was f3d = fModel.f3d if self.loadPal: savePaletteLoad(loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) @@ -866,8 +866,8 @@ def writeAll( + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." ) - self.ti0.writeAll(fMaterial.getTexturesGfxList(0), fMaterial, fModel, convertTextureData) - self.ti1.writeAll(fMaterial.getTexturesGfxList(1), fMaterial, fModel, convertTextureData) + self.ti0.writeAll(fMaterial.texture_DL, fMaterial, fModel, convertTextureData) + self.ti1.writeAll(fMaterial.texture_DL, fMaterial, fModel, convertTextureData) def getTexDimensions(self): return self.texDimensions diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 66781b275..619d4fd65 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -123,7 +123,7 @@ def processGfxScrollCommand(self, commandIndex: int, command: GbiMacro, gfxListN return "", "" elif tags & (GfxTag.TileScroll0 | GfxTag.TileScroll1): textureIndex = 0 if tags & GfxTag.TileScroll0 else 1 - return get_tile_scroll_code(fMaterial.texture_DLs[textureIndex].name, fMaterial.scrollData, textureIndex, commandIndex) + return get_tile_scroll_code(fMaterial.texture_DL.name, fMaterial.scrollData, textureIndex, commandIndex) else: return "", "" From 2238313de8dc84adcef515420ca81a1187df29a1 Mon Sep 17 00:00:00 2001 From: scut Date: Fri, 17 Mar 2023 13:33:24 -0400 Subject: [PATCH 35/38] tile scroll gfx tag only will apply if tile scroll exists in material --- fast64_internal/f3d/f3d_bleed.py | 1 - fast64_internal/f3d/f3d_texture_writer.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index 14e6e4891..dd0cd77fc 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -260,7 +260,6 @@ def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: in return True def bleed_DPSetTileSize(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - print(cmd.tags, cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1) return cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1 def bleed_DPSetTile(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index be60cb1d8..1c41e063e 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -1017,7 +1017,11 @@ def saveTextureTile( tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) - tileSizeCommand.tags |= GfxTag.TileScroll0 if rendertile == 0 else GfxTag.TileScroll1 + + scrollInfo = getattr(fMaterial.scrollData, f"tile_scroll_tex{rendertile}") + if scrollInfo.s or scrollInfo.t: + tileSizeCommand.tags |= GfxTag.TileScroll0 if rendertile == 0 else GfxTag.TileScroll1 + tileSizeCommand.fMaterial = fMaterial if not omitSetTile: gfxOut.commands.append(tileCommand) From c0089b13a25002d44a4743fe248d248233b30f5a Mon Sep 17 00:00:00 2001 From: Sauraen Date: Sun, 19 Mar 2023 10:48:38 -0700 Subject: [PATCH 36/38] Fixed superfluous passing of loadGfx to writeAll --- fast64_internal/f3d/f3d_texture_writer.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index 1c41e063e..2623a791d 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -497,7 +497,6 @@ def getPaletteName(self): def writeAll( self, - loadGfx: GfxList, fMaterial: FMaterial, fModel: Union[FModel, FTexRect], convertTextureData: bool, @@ -517,7 +516,7 @@ def writeAll( ) # Write loads - # loadGfx = fMaterial.mat_only_DL # I don't know what the point of this was + loadGfx = fMaterial.texture_DL f3d = fModel.f3d if self.loadPal: savePaletteLoad(loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) @@ -866,8 +865,8 @@ def writeAll( + "bytes, ex. 2 32x32 RGBA 16 bit textures.\nNote that texture width will be internally padded to 64 bit boundaries." ) - self.ti0.writeAll(fMaterial.texture_DL, fMaterial, fModel, convertTextureData) - self.ti1.writeAll(fMaterial.texture_DL, fMaterial, fModel, convertTextureData) + self.ti0.writeAll(fMaterial, fModel, convertTextureData) + self.ti1.writeAll(fMaterial, fModel, convertTextureData) def getTexDimensions(self): return self.texDimensions From e124a4dae8d337a0172cde07b8a86745645b3ee8 Mon Sep 17 00:00:00 2001 From: scut Date: Mon, 3 Apr 2023 21:13:53 -0400 Subject: [PATCH 37/38] added else statement to bleed case for textures when large textures are used --- fast64_internal/f3d/f3d_bleed.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index dd0cd77fc..aebbbdead 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -71,6 +71,8 @@ def bleed_fmesh(self, f3d: F3D, fMesh: FMesh, lastMat: FMaterial, cmd_list: GfxL bleed_gfx_lists.bled_mats = self.bleed_mat(triGroup.fMaterial, lastMat, cmd_list, bleed_state) if not (triGroup.fMaterial.isTexLarge[0] or triGroup.fMaterial.isTexLarge[1]): bleed_gfx_lists.bled_tex = self.bleed_textures(triGroup.fMaterial, lastMat, cmd_list, bleed_state) + else: + bleed_gfx_lists.bled_tex = triGroup.fMaterial.texture_DL.commands lastMat = triGroup.fMaterial # bleed tri group (for large textures) and to remove other unnecessary cmds self.bleed_tri_group(f3d, triGroup, bleed_gfx_lists, cmd_list, bleed_state) From c87fc7c6a26369c47d3f1bf216c8f2a43d9bfbf3 Mon Sep 17 00:00:00 2001 From: Sauraen Date: Tue, 4 Apr 2023 16:22:57 -0700 Subject: [PATCH 38/38] Changes for cozies --- fast64_internal/f3d/f3d_gbi.py | 21 +++---- fast64_internal/f3d/f3d_texture_writer.py | 71 ++++++++++++++--------- fast64_internal/sm64/sm64_f3d_writer.py | 12 +++- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 6f2532f77..1b415724c 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -1764,8 +1764,7 @@ def get_sts_interval_vars(tex_num: str): def get_tex_sts_code( - variableName : str, - tex: FSetTileSizeScrollField, cmd_num: int + variableName: str, tex: FSetTileSizeScrollField, cmd_num: int ) -> Tuple[list[str], list[Tuple[str, float]]]: variables = [] # create func calls @@ -1794,7 +1793,9 @@ def get_tex_sts_code( return variables, lines -def get_tile_scroll_code(variableName : str, scrollData: "FScrollData", textureIndex: int, commandIndex: int) -> Tuple[str, str]: +def get_tile_scroll_code( + variableName: str, scrollData: "FScrollData", textureIndex: int, commandIndex: int +) -> Tuple[str, str]: scrollInfo: FSetTileSizeScrollField = getattr(scrollData, f"tile_scroll_tex{textureIndex}") if scrollInfo.s or scrollInfo.t: variables = [] @@ -2268,8 +2269,8 @@ def processTexRefNonCITextures(self, fMaterial: FMaterial, material: bpy.types.M - an object containing info about the additional textures, or None """ texProp = getattr(material.f3d_mat, f"tex{index}") - imUse = [] if texProp.tex is None else [texProp.tex] - return imUse, None + imDependencies = [] if texProp.tex is None else [texProp.tex] + return imDependencies, None def writeTexRefNonCITextures(self, obj, texFmt: str): """ @@ -2288,8 +2289,8 @@ def processTexRefCITextures(self, fMaterial: FMaterial, material: bpy.types.Mate - the palette to use (or None) """ texProp = getattr(material.f3d_mat, f"tex{index}") - imUse = [] if texProp.tex is None else [texProp.tex] - return imUse, None, None + imDependencies = [] if texProp.tex is None else [texProp.tex] + return imDependencies, None, None def writeTexRefCITextures( self, @@ -2611,7 +2612,7 @@ def to_c_vertex_scroll(self, gfxFormatter: GfxFormatter) -> CScrollData: def to_c_gfx_scroll(self, gfxFormatter: GfxFormatter) -> CScrollData: data = CScrollData() - for (fMaterial, _) in self.materials.values(): + for fMaterial, _ in self.materials.values(): fMaterial: FMaterial if fMaterial.material: data.append(gfxFormatter.gfxScrollToC(fMaterial.material, self.f3d)) @@ -2745,7 +2746,6 @@ def create_data(self): sortedList = sorted(self.lodEntries, key=lambda tup: tup[0]) hasAnyDLs = False for item in sortedList: - # If no DLs are called, we still need an empty DL to preserve LOD. if len(item[1].commands) < 2: DL = item[1] @@ -3397,6 +3397,7 @@ def to_binary(self, f3d, segments): # SPSprite2DBase + # RSP short command (no DMA required) macros def gsImmp0(c): words = _SHIFTL((c), 24, 8), 0 @@ -4326,7 +4327,7 @@ class DPSetTextureImage(GbiMacro): width: int image: FImage _segptrs = True # calls segmented_to_virtual on name when needed - + def to_binary(self, f3d, segments): fmt = f3d.G_IM_FMT_VARS[self.fmt] siz = f3d.G_IM_SIZ_VARS[self.siz] diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index 2623a791d..1b2140a05 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -138,6 +138,16 @@ def initWithFace(self, obj, face): self.offsets.append((soffset, toffset)) def trySubsume(self, other): + """ + Attempts to enlarge the self TileLoad to cover both itself and the other + TileLoad. If this succeeds, self is modified and True is returned. If it + fails (because it would be too large or the other constraints from + fixRegion would be violated), self is not modified and False is returned. + A large texture mesh is built by, for each triangle, trying to subsume + it into each of the existing loads. If it succeeds on one of them, it + moves on to the next triangle. If it fails on all of them, a new load is + created for that triangle and added to the list. + """ # Could do fancier logic checking across borders, for example if we have # one loading 60-68 (size 64) and another 0-8, that could be merged to # one load 60-72. But this is likely to be uncommon and won't be generated @@ -167,6 +177,11 @@ def maybeSaveSingleLargeTextureSetup( curImgSet: Optional[int], curTileLines: list[int], ): + """ + Checks whether a particular texture is large and if so, writes the loads for + that large texture. "maybe" is to bring the if statement into the function + instead of checking whether the texture is large before calling it. + """ if fMaterial.isTexLarge[i]: wrapS = tileSettings.sh >= texDimensions[0] wrapT = tileSettings.th >= texDimensions[1] @@ -175,10 +190,6 @@ def maybeSaveSingleLargeTextureSetup( siz = texBitSizeF3D[texProp.tex_format] line = getTileLine(fImage, tileSettings.sl, tileSettings.sh, siz, fModel.f3d) tmem = fMaterial.largeTexAddr[i] - # print( - # f"Tile: {tileSettings.sl}-{tileSettings.sh} x {tileSettings.tl}-{tileSettings.th} " - # + f"tmem {tmem} line {line}" - # ) if wrapS or wrapT: fmt = texFormatOf[texProp.tex_format] texelsPerWord = 64 // texBitSizeInt[texProp.tex_format] @@ -206,7 +217,6 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # The first load must occupy a whole number of lines. assert (texDimensions[0] - tileSettings.sl) % texelsPerWord == 0 sLineOfs = (texDimensions[0] - tileSettings.sl) // texelsPerWord - # print(f"-- Wrap at S={texDimensions[0]}, offset {sLineOfs}") gfxOut.commands.append( DPLoadTile(tidxBase, tileSettings.sl * sm, TL * 4, (texDimensions[0] - 1) * sm, TH * 4) ) @@ -227,7 +237,6 @@ def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): # The first load must be even in size (even number of texture rows). assert (texDimensions[1] - tileSettings.tl) % 2 == 0 tLineOfs = line * (texDimensions[1] - tileSettings.tl) - # print(f"-- Wrap at T={texDimensions[1]}, offset {tLineOfs}") loadOneOrTwoS(tmem, 7, tileSettings.tl, texDimensions[1] - 1) loadOneOrTwoS(tmem + tLineOfs, 5, 0, tileSettings.th - texDimensions[1]) else: @@ -316,7 +325,6 @@ def saveOrGetPaletteDefinition( palBaseName: str, palLen: int, ) -> tuple[FPaletteKey, FImage]: - texFmt = texProp.tex_format palFmt = texProp.ci_format palFormat = texFormatOf[palFmt] @@ -346,7 +354,6 @@ def saveOrGetTextureDefinition( images: list[bpy.types.Image], isLarge: bool, ) -> tuple[FImageKey, FImage]: - image = texProp.tex texFmt = texProp.tex_format texFormat = texFormatOf[texFmt] @@ -387,7 +394,7 @@ class TexInfo: # Parameters from moreSetupFromModel pal: Optional[list[int]] = None palLen: int = 0 - imUse: Optional[list[bpy.types.Image]] = None + imDependencies: Optional[list[bpy.types.Image]] = None flipbook: Optional["TextureFlipbook"] = None isPalRef: bool = False @@ -395,7 +402,7 @@ class TexInfo: texAddr: int = 0 palAddr: int = 0 palIndex: int = 0 - palUse: list[bpy.types.Image] = field(default_factory=list) + palDependencies: list[bpy.types.Image] = field(default_factory=list) palBaseName: str = "" loadPal: bool = False doTexLoad: bool = True @@ -466,7 +473,9 @@ def moreSetupFromModel( return if self.isTexCI: - self.imUse, self.flipbook, self.pal = fModel.processTexRefCITextures(fMaterial, material, self.indexInMat) + self.imDependencies, self.flipbook, self.pal = fModel.processTexRefCITextures( + fMaterial, material, self.indexInMat + ) if self.isTexRef: if self.flipbook is not None: self.palLen = len(self.pal) @@ -483,10 +492,10 @@ def moreSetupFromModel( + f" uses too many unique colors to fit in format {self.texFormat}." ) else: - self.imUse, self.flipbook = fModel.processTexRefNonCITextures(fMaterial, material, self.indexInMat) + self.imDependencies, self.flipbook = fModel.processTexRefNonCITextures(fMaterial, material, self.indexInMat) self.isPalRef = self.isTexRef and self.flipbook is None - self.palUse = self.imUse + self.palDependencies = self.imDependencies def getPaletteName(self): if not self.useTex or self.isPalRef: @@ -503,16 +512,18 @@ def writeAll( ): if not self.useTex: return - assert self.imUse is not None # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imUse = [tex] + assert ( + self.imDependencies is not None + ) # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imDependencies = [tex] # Get definitions imageKey, fImage = saveOrGetTextureDefinition( - fMaterial, fModel, self.texProp, self.imUse, fMaterial.isTexLarge[self.indexInMat] + fMaterial, fModel, self.texProp, self.imDependencies, fMaterial.isTexLarge[self.indexInMat] ) fMaterial.imageKey[self.indexInMat] = imageKey if self.loadPal: _, fPalette = saveOrGetPaletteDefinition( - fMaterial, fModel, self.texProp, self.isPalRef, self.palUse, self.palBaseName, self.palLen + fMaterial, fModel, self.texProp, self.isPalRef, self.palDependencies, self.palBaseName, self.palLen ) # Write loads @@ -534,7 +545,7 @@ def writeAll( if self.isTexRef: if self.isTexCI: fModel.writeTexRefCITextures( - self.flipbook, fMaterial, self.imUse, self.pal, self.texFormat, self.palFormat + self.flipbook, fMaterial, self.imDependencies, self.pal, self.texFormat, self.palFormat ) else: fModel.writeTexRefNonCITextures(self.flipbook, self.texFormat) @@ -654,9 +665,11 @@ def writeAll( + str(self.ti0.palLen) + " colors, which can't fit in a CI8 palette (256)." ) - # self.ti0.imUse remains what it was; the CIs in im0 are the same as they + # self.ti0.imDependencies remains what it was; the CIs in im0 are the same as they # would be if im0 was alone. But im1 and self.ti0.pal depend on both. - self.ti1.imUse = self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + self.ti1.imDependencies = self.ti0.palDependencies = ( + self.ti0.imDependencies + self.ti1.imDependencies + ) elif self.ti0.texFormat != self.ti1.texFormat: # One CI8, one CI4 ci8Pal, ci4Pal = ( (self.ti0.pal, self.ti1.pal) if self.ti0.texFormat == "CI8" else (self.ti1.pal, self.ti0.pal) @@ -697,11 +710,11 @@ def writeAll( ) # The use for the CI4 texture remains what it was; its CIs are the # same as if it was alone. But both the palette and the CI8 CIs are affected. - self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + self.ti0.palDependencies = self.ti0.imDependencies + self.ti1.imDependencies if self.ti0.texFormat == "CI8": - self.ti0.imUse = self.ti0.palUse + self.ti0.imDependencies = self.ti0.palDependencies else: - self.ti1.imUse = self.ti0.palUse + self.ti1.imDependencies = self.ti0.palDependencies else: # both CI4 textures if ( self.ti0.pal is None @@ -722,9 +735,11 @@ def writeAll( # Share palette 0 self.ti0.pal = tempPal self.ti0.palLen = tempPalLen - # self.ti0.imUse remains what it was; the CIs in im0 are the same as they + # self.ti0.imDependencies remains what it was; the CIs in im0 are the same as they # would be if im0 was alone. But im1 and self.ti0.pal depend on both. - self.ti1.imUse = self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + self.ti1.imDependencies = self.ti0.palDependencies = ( + self.ti0.imDependencies + self.ti1.imDependencies + ) else: # Load one palette across 0-1. Put the longer in slot 0 if self.ti0.palLen >= self.ti1.palLen: @@ -741,7 +756,7 @@ def writeAll( self.ti0.palIndex = 1 # The up-to-32 entries in self.ti0.pal depend on both images. But the # CIs in both im0 and im1 are the same as if there was no shared palette. - self.ti0.palUse = self.ti0.imUse + self.ti1.imUse + self.ti0.palDependencies = self.ti0.imDependencies + self.ti1.imDependencies fMaterial.texPaletteIndex = [self.ti0.palIndex, self.ti1.palIndex] self.ti0.palBaseName = self.ti0.getPaletteName() self.ti1.palBaseName = self.ti1.getPaletteName() @@ -1016,11 +1031,11 @@ def saveTextureTile( tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) - + scrollInfo = getattr(fMaterial.scrollData, f"tile_scroll_tex{rendertile}") if scrollInfo.s or scrollInfo.t: tileSizeCommand.tags |= GfxTag.TileScroll0 if rendertile == 0 else GfxTag.TileScroll1 - + tileSizeCommand.fMaterial = fMaterial if not omitSetTile: gfxOut.commands.append(tileCommand) @@ -1220,7 +1235,7 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): raise PluginError("Invalid combo: " + fmt + ", " + bitSize) elif fmt == "G_IM_FMT_CI": - raise PluginError("CI not yet implemented.") + raise PluginError("Internal error, writeNonCITextureData called for CI image.") elif fmt == "G_IM_FMT_IA": if bitSize == "G_IM_SIZ_4b": diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 619d4fd65..8feaa89e0 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -313,7 +313,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): raise PluginError(f"In {name}: texture is too big (> 4 KiB).") if ti.texFormat != "RGBA16": raise PluginError(f"In {name}: texture format must be RGBA16 (because copy mode).") - ti.imUse = [tex] + ti.imDependencies = [tex] ti.writeAll(fTexRect.draw, fMaterial, fTexRect, convertTextureData) fTexRect.draw.commands.append( @@ -358,13 +358,19 @@ def sm64ExportF3DtoC( dirPath, texDir = getExportDir(customExport, basePath, headerType, levelName, texDir, name) inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model(f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll) + fModel = SM64Model( + f3dType, + isHWv1, + name, + DLFormat, + GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + ) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, name, DLFormat, not savePNG) if inline: bleed_gfx = BleedGraphics() bleed_gfx.bleed_fModel(fModel, fMeshes) - + modelDirPath = os.path.join(dirPath, toAlnum(name)) if not os.path.exists(modelDirPath):