diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 1eb5b7d80..d77096864 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -3262,6 +3262,10 @@ class FImage: isLargeTexture: bool = field(init=False, compare=False, default=False) converted: bool = field(init=False, compare=False, default=False) + @property + def aligner_name(self): + return f"{self.name}_aligner" + def size(self): return len(self.data) @@ -3280,7 +3284,7 @@ def to_c_helper(self, texData, bitsPerValue): # This is to force 8 byte alignment if bitsPerValue != 64: - code.source = f"Gfx {self.name}_aligner[] = {{gsSPEndDisplayList()}};\n" + code.source = f"Gfx {self.aligner_name}[] = {{gsSPEndDisplayList()}};\n" code.source += f"u{str(bitsPerValue)} {self.name}[] = {{\n\t" code.source += texData code.source += "\n};\n\n" @@ -3399,7 +3403,8 @@ def to_c(self, static=True): if static: return f"g{'s'*static}{type(self).__name__}({', '.join( self.getargs(static) )})" else: - return f"g{'s'*static}{type(self).__name__}(glistp++, {', '.join( self.getargs(static) )})" + args = ["glistp++"] + list(self.getargs(static)) + return f"g{'s'*static}{type(self).__name__}({', '.join( args )})" def size(self, f3d): return GFX_SIZE diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index f4be233ed..9c1db5347 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -2143,9 +2143,9 @@ def get_textlut_mode(f3d_mat: "F3DMaterialProperty", inherit_from_tex: bool = Fa use_dict = all_combiner_uses(f3d_mat) textures = [f3d_mat.tex0] if use_dict["Texture 0"] and f3d_mat.tex0.tex_set else [] textures += [f3d_mat.tex1] if use_dict["Texture 1"] and f3d_mat.tex1.tex_set else [] - tlut_modes = [tex.ci_format if tex.tex_format.startswith("CI") else "NONE" for tex in textures] + tlut_modes = [tex.tlut_mode for tex in textures] if tlut_modes and tlut_modes[0] == tlut_modes[-1]: - return "G_TT_" + tlut_modes[0] + return tlut_modes[0] return None if inherit_from_tex else f3d_mat.rdp_settings.g_mdsft_textlut @@ -2858,6 +2858,15 @@ class TextureProperty(PropertyGroup): ) tile_scroll: bpy.props.PointerProperty(type=SetTileSizeScrollProperty) + @property + def is_ci(self): + self.tex_format: str + return self.tex_format.startswith("CI") + + @property + def tlut_mode(self): + return f"G_TT_{self.ci_format if self.is_ci else 'NONE'}" + def get_tex_size(self) -> list[int]: if self.tex or self.use_tex_reference: if self.tex is not None: @@ -2919,6 +2928,7 @@ def ui_image( textureProp: TextureProperty, name: str, showCheckBox: bool, + hide_lowhigh=False, ): inputGroup = layout.box().column() @@ -3025,7 +3035,8 @@ def ui_image( shift = prop_input.row() shift.prop(textureProp.S, "shift", text="Shift S") shift.prop(textureProp.T, "shift", text="Shift T") - + if hide_lowhigh: + return low = prop_input.row() low.prop(textureProp.S, "low", text="S Low") low.prop(textureProp.T, "low", text="T Low") diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index d08d80ced..eabafd92c 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -420,10 +420,10 @@ def fromMat(self, index: int, f3dMat: F3DMaterialProperty) -> bool: texProp = getattr(f3dMat, "tex" + str(index)) return self.fromProp(texProp, index) - def fromProp(self, texProp: TextureProperty, index: int) -> bool: + def fromProp(self, texProp: TextureProperty, index: int, ignore_tex_set=False) -> bool: self.indexInMat = index self.texProp = texProp - if not texProp.tex_set: + if not texProp.tex_set and not ignore_tex_set: return True self.useTex = True @@ -463,6 +463,34 @@ def fromProp(self, texProp: TextureProperty, index: int) -> bool: return True + def materialless_setup(self) -> None: + """moreSetupFromModel equivalent that does not handle material properties like OOT flipbooks""" + if not self.useTex: + return + + if self.isTexCI: + self.imDependencies, self.flipbook, self.pal = ( + [] if self.texProp.tex is None else [self.texProp.tex], + None, + None, + ) + if self.isTexRef: + 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 Texture {self.indexInMat} uses too many unique colors to fit in format {self.texFormat}." + ) + else: + self.imDependencies = [] if self.texProp.tex is None else [self.texProp.tex] + self.flipbook = None + + self.isPalRef = self.isTexRef and self.flipbook is None + self.palDependencies = self.imDependencies + def moreSetupFromModel( self, material: bpy.types.Material, @@ -504,6 +532,26 @@ def getPaletteName(self): return self.flipbook.name return getImageName(self.texProp.tex) + def setup_single_tex(self, is_ci: bool, use_large_tex: bool): + is_large = False + tmem_size = 256 if is_ci else 512 + if is_ci: + assert self.useTex # should this be here? + if self.useTex: + self.loadPal = True + self.palBaseName = self.getPaletteName() + if self.tmemSize > tmem_size: + if use_large_tex: + self.doTexLoad = False + return True + elif not bpy.context.scene.ignoreTextureRestrictions: + raise PluginError( + "Textures are too big. Max TMEM size is 4k " + "bytes, ex. 2 32x32 RGBA 16 bit textures.\n" + "Note that texture width will be internally padded to 64 bit boundaries." + ) + return is_large + def writeAll( self, fMaterial: FMaterial, @@ -514,7 +562,7 @@ def writeAll( return assert ( self.imDependencies is not None - ) # Must be set manually if didn't use moreSetupFromModel, e.g. ti.imDependencies = [tex] + ), "self.imDependencies is None, either moreSetupFromModel or materialless_setup must be called beforehand" # Get definitions imageKey, fImage = saveOrGetTextureDefinition( @@ -551,6 +599,9 @@ def writeAll( fModel.writeTexRefNonCITextures(self.flipbook, self.texFormat) else: if self.isTexCI: + assert ( + self.pal is not None + ), "self.pal is None, either moreSetupFromModel or materialless_setup must be called beforehand" writeCITextureData(self.texProp.tex, fImage, self.pal, self.palFormat, self.texFormat) else: writeNonCITextureData(self.texProp.tex, fImage, self.texFormat) diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index ab69b095f..e71c3202a 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -1,15 +1,16 @@ +from pathlib import Path import shutil, copy, bpy, re, os from io import BytesIO from math import ceil, log, radians from mathutils import Matrix, Vector from bpy.utils import register_class, unregister_class from ..panels import SM64_Panel -from ..f3d.f3d_writer import exportF3DCommon +from ..f3d.f3d_writer import exportF3DCommon, saveModeSetting from ..f3d.f3d_texture_writer import TexInfo from ..f3d.f3d_material import ( TextureProperty, - tmemUsageUI, all_combiner_uses, + ui_image, ui_procAnim, update_world_default_rendermode, ) @@ -22,6 +23,8 @@ from ..f3d.f3d_bleed import BleedGraphics from ..f3d.f3d_gbi import ( + DPSetCombineMode, + DPSetTextureLUT, get_F3D_GBI, GbiMacro, GfxTag, @@ -53,6 +56,7 @@ ) from ..utility import ( + CData, CScrollData, PluginError, raisePluginError, @@ -99,7 +103,7 @@ # filepath, function to insert before enumHUDPaths = { "HUD": ("src/game/hud.c", "void render_hud(void)"), - "Menu": ("src/game/ingame_menu.c", "s16 render_menus_and_dialogs()"), + "Menu": ("src/game/ingame_menu.c", "s16 render_menus_and_dialogs("), } @@ -167,13 +171,11 @@ def exportTexRectToC(dirPath, texProp, texDir, savePNG, name, exportToProject, p if name is None or name == "": raise PluginError("Name cannot be empty.") - exportData = fTexRect.to_c(savePNG, texDir, SM64GfxFormatter(ScrollMethod.Vertex)) - staticData = exportData.staticData - dynamicData = exportData.dynamicData + formater = SM64GfxFormatter(ScrollMethod.Vertex) - declaration = staticData.header + dynamicData = CData() + dynamicData.append(fTexRect.draw.to_c(fTexRect.f3d)) code = modifyDLForHUD(dynamicData.source) - data = staticData.source if exportToProject: seg2CPath = os.path.join(dirPath, "bin/segment2.c") @@ -186,22 +188,41 @@ def exportTexRectToC(dirPath, texProp, texDir, savePNG, name, exportToProject, p checkIfPathExists(seg2TexDir) checkIfPathExists(hudPath) - fTexRect.save_textures(seg2TexDir, not savePNG) + if savePNG: + fTexRect.save_textures(seg2TexDir) - textures = [] + include_dir = Path(texDir).as_posix() + "/" for _, fImage in fTexRect.textures.items(): - textures.append(fImage) - - # Append/Overwrite texture definition to segment2.c - overwriteData("const\s*u8\s*", textures[0].name, data, seg2CPath, None, False) + if savePNG: + data = fImage.to_c_tex_separate(include_dir, formater.texArrayBitSize) + else: + data = fImage.to_c(formater.texArrayBitSize) + + # Append/Overwrite texture definition to segment2.c + overwriteData( + rf"(Gfx\s+{fImage.aligner_name}\s*\[\s*\]\s*=\s*\{{\s*gsSPEndDisplayList\s*\(\s*\)\s*\}}\s*;\s*)?" + rf"u{str(formater.texArrayBitSize)}\s*", + fImage.name, + data.source, + seg2CPath, + None, + False, + post_regex=r"\s?\s?", # tex to c includes 2 newlines + ) - # Append texture declaration to segment2.h - writeIfNotFound(seg2HPath, declaration, "#endif") + # Append texture declaration to segment2.h + writeIfNotFound(seg2HPath, data.header, "#endif") # Write/Overwrite function to hud.c - overwriteData("void\s*", fTexRect.name, code, hudPath, projectExportData[1], True) + overwriteData("void\s*", fTexRect.name, code, hudPath, projectExportData[1], True, post_regex=r"\s?") else: + exportData = fTexRect.to_c(savePNG, texDir, formater) + staticData = exportData.staticData + + declaration = staticData.header + data = staticData.source + singleFileData = "" singleFileData += "// Copy this function to src/game/hud.c or src/game/ingame_menu.c.\n" singleFileData += "// Call the function in render_hud() or render_menus_and_dialogs() respectively.\n" @@ -275,73 +296,55 @@ def modifyDLForHUD(data): # data = data[:matchResult.start(7)] + 'segmented_to_virtual(&' + \ # matchResult.group(7) + ")" +data[matchResult.end(7):] - return data + return data.removesuffix("\n") def exportTexRectCommon(texProp, name, convertTextureData): - tex = texProp.tex - if tex is None: - raise PluginError("No texture is selected.") + use_copy_mode = texProp.tlut_mode == "G_TT_RGBA16" or texProp.tex_format == "RGBA16" - texProp.S.low = 0 - texProp.S.high = texProp.tex.size[0] - 1 - texProp.S.mask = ceil(log(texProp.tex.size[0], 2) - 0.001) - texProp.S.shift = 0 - - texProp.T.low = 0 - texProp.T.high = texProp.tex.size[1] - 1 - texProp.T.mask = ceil(log(texProp.tex.size[1], 2) - 0.001) - texProp.T.shift = 0 + defaults = create_or_get_world(bpy.context.scene).rdp_defaults fTexRect = FTexRect(toAlnum(name), GfxMatWriteMethod.WriteDifferingAndRevert) - fMaterial = FMaterial(toAlnum(name) + "_mat", DLFormat.Dynamic) - - # dl_hud_img_begin - fTexRect.draw.commands.extend( - [ - DPPipeSync(), - DPSetCycleType("G_CYC_COPY"), - DPSetTexturePersp("G_TP_NONE"), - DPSetAlphaCompare("G_AC_THRESHOLD"), - DPSetBlendColor(0xFF, 0xFF, 0xFF, 0xFF), - DPSetRenderMode(["G_RM_AA_XLU_SURF", "G_RM_AA_XLU_SURF2"], None), - ] - ) + fMaterial = fTexRect.addMaterial(toAlnum(name) + "_mat") - drawEndCommands = GfxList("temp", GfxListTag.Draw, DLFormat.Dynamic) + # use_copy_mode is based on dl_hud_img_begin and dl_hud_img_end + if use_copy_mode: + saveModeSetting(fMaterial, "G_CYC_COPY", defaults.g_mdsft_cycletype, DPSetCycleType) + else: + saveModeSetting(fMaterial, "G_CYC_1CYCLE", defaults.g_mdsft_cycletype, DPSetCycleType) + fMaterial.mat_only_DL.commands.append( + DPSetCombineMode(*fTexRect.f3d.G_CC_DECALRGBA, *fTexRect.f3d.G_CC_DECALRGBA) + ) + fMaterial.revert.commands.append(DPSetCombineMode(*fTexRect.f3d.G_CC_SHADE, *fTexRect.f3d.G_CC_SHADE)) + saveModeSetting(fMaterial, "G_TP_NONE", defaults.g_mdsft_textpersp, DPSetTexturePersp) + saveModeSetting(fMaterial, "G_AC_THRESHOLD", defaults.g_mdsft_alpha_compare, DPSetAlphaCompare) + fMaterial.mat_only_DL.commands.append(DPSetBlendColor(0xFF, 0xFF, 0xFF, 0xFF)) + fMaterial.mat_only_DL.commands.append(DPSetRenderMode(["G_RM_AA_XLU_SURF", "G_RM_AA_XLU_SURF2"], None)) + fMaterial.revert.commands.append(DPSetRenderMode(["G_RM_AA_ZB_OPA_SURF", "G_RM_AA_ZB_OPA_SURF2"], None)) + + saveModeSetting(fMaterial, texProp.tlut_mode, defaults.g_mdsft_textlut, DPSetTextureLUT) 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 (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 (because copy mode).") - ti.imDependencies = [tex] - ti.writeAll(fTexRect.draw, fMaterial, fTexRect, convertTextureData) + ti.fromProp(texProp, index=0, ignore_tex_set=True) + ti.materialless_setup() + ti.setup_single_tex(texProp.is_ci, False) + ti.writeAll(fMaterial, fTexRect, convertTextureData) + fTexRect.materials[texProp] = (fMaterial, ti.imageDims) + + if use_copy_mode: + dsdx = 4 << 10 + dtdy = 1 << 10 + else: + dsdx = dtdy = 4096 // 4 + fTexRect.draw.commands.extend(fMaterial.mat_only_DL.commands) + fTexRect.draw.commands.extend(fMaterial.texture_DL.commands) fTexRect.draw.commands.append( - SPScisTextureRectangle(0, 0, (texDimensions[0] - 1) << 2, (texDimensions[1] - 1) << 2, 0, 0, 0) - ) - - fTexRect.draw.commands.extend(drawEndCommands.commands) - - # dl_hud_img_end - fTexRect.draw.commands.extend( - [ - DPPipeSync(), - DPSetCycleType("G_CYC_1CYCLE"), - SPTexture(0xFFFF, 0xFFFF, 0, "G_TX_RENDERTILE", "G_OFF"), - DPSetTexturePersp("G_TP_PERSP"), - DPSetAlphaCompare("G_AC_NONE"), - DPSetRenderMode(["G_RM_AA_ZB_OPA_SURF", "G_RM_AA_ZB_OPA_SURF2"], None), - SPEndDisplayList(), - ] + SPScisTextureRectangle(0, 0, (ti.imageDims[0] - 1) << 2, (ti.imageDims[1] - 1) << 2, 0, 0, 0, dsdx, dtdy) ) + fTexRect.draw.commands.append(DPPipeSync()) + fTexRect.draw.commands.extend(fMaterial.revert.commands) + fTexRect.draw.commands.append(SPEndDisplayList()) return fTexRect @@ -822,25 +825,7 @@ class ExportTexRectDrawPanel(SM64_Panel): # called every frame def draw(self, context): col = self.layout.column() - propsTexRectE = col.operator(ExportTexRectDraw.bl_idname) - - textureProp = context.scene.texrect - tex = textureProp.tex - col.label(text="This is for decomp only.") - col.template_ID(textureProp, "tex", new="image.new", open="image.open", unlink="image.texrect_unlink") - # col.prop(textureProp, 'tex') - - tmemUsageUI(col, textureProp) - if tex is not None and tex.size[0] > 0 and tex.size[1] > 0: - col.prop(textureProp, "tex_format", text="Format") - if textureProp.tex_format[:2] == "CI": - col.prop(textureProp, "ci_format", text="CI Format") - col.prop(textureProp.S, "clamp", text="Clamp S") - col.prop(textureProp.T, "clamp", text="Clamp T") - col.prop(textureProp.S, "mirror", text="Mirror S") - col.prop(textureProp.T, "mirror", text="Mirror T") - prop_split(col, context.scene, "TexRectName", "Name") col.prop(context.scene, "TexRectCustomExport") if context.scene.TexRectCustomExport: col.prop(context.scene, "TexRectExportPath") @@ -857,6 +842,9 @@ def draw(self, context): infoBox.label(text="After export, call your hud's draw function in ") infoBox.label(text=enumHUDPaths[context.scene.TexRectExportType][0] + ": ") infoBox.label(text=enumHUDPaths[context.scene.TexRectExportType][1] + ".") + prop_split(col, context.scene, "TexRectName", "Name") + ui_image(False, col, None, context.scene.texrect, context.scene.TexRectName, False, hide_lowhigh=True) + col.operator(ExportTexRectDraw.bl_idname) class SM64_DrawLayersPanel(bpy.types.Panel): diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 14f6aea59..f47b9dc70 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -712,7 +712,7 @@ def getExportDir(customExport, dirPath, headerType, levelName, texDir, dirName): return dirPath, texDir -def overwriteData(headerRegex, name, value, filePath, writeNewBeforeString, isFunction): +def overwriteData(headerRegex, name, value, filePath, writeNewBeforeString, isFunction, post_regex=""): if os.path.exists(filePath): dataFile = open(filePath, "r") data = dataFile.read() @@ -721,7 +721,8 @@ def overwriteData(headerRegex, name, value, filePath, writeNewBeforeString, isFu matchResult = re.search( headerRegex + re.escape(name) - + ("\s*\((((?!\)).)*)\)\s*\{(((?!\}).)*)\}" if isFunction else "\[\]\s*=\s*\{(((?!;).)*);"), + + ("\s*\((((?!\)).)*)\)\s*\{(((?!\}).)*)\}" if isFunction else "\[\]\s*=\s*\{(((?!;).)*);") + + post_regex, data, re.DOTALL, )