Skip to content

Commit

Permalink
Prune unused textures and materials
Browse files Browse the repository at this point in the history
  • Loading branch information
achilleasa committed Aug 14, 2016
1 parent 790d484 commit 76d9467
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 66 deletions.
72 changes: 60 additions & 12 deletions scene/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,65 @@ func Compile(parsedScene *scene.ParsedScene) (*scene.Scene, error) {
return compiler.optimizedScene, nil
}

// Iterate through all materials that are in use and flag their textures as being in use.
// This method will also manually flag the special scene diffuse/emissive materials as
// being in use as no primitives reference them.
func (sc *sceneCompiler) flagUsedTextures() {
for _, mat := range sc.parsedScene.Materials {
if mat.Name == sceneDiffuseMaterialName || mat.Name == sceneEmissiveMaterialName {
mat.Used = true
}

if mat.Used {
mat.MarkTexturesAsUsed()
}
}
}

// Allocate a contiguous memory block for all texture data and initialize the
// scene's texture metadata so that they point to the proper index inside the block.
func (sc *sceneCompiler) bakeTextures() error {
start := time.Now()
sc.logger.Noticef("processing %d textures", len(sc.parsedScene.Textures))

sc.flagUsedTextures()

// Find how much memory we need. To ensure proper memory alignment we pad
// each texture's data len so its a multiple of a qword
var totalDataLen uint32 = 0
activeTextures := 0
for _, tex := range sc.parsedScene.Textures {
// Skip unused textures
if !tex.Used {
continue
}
totalDataLen += align4(len(tex.Data))
activeTextures++
}

sc.optimizedScene.TextureData = make([]byte, totalDataLen)
sc.optimizedScene.TextureMetadata = make([]scene.TextureMetadata, len(sc.parsedScene.Textures))
sc.optimizedScene.TextureMetadata = make([]scene.TextureMetadata, activeTextures)
var offset uint32 = 0
for index, tex := range sc.parsedScene.Textures {
sc.optimizedScene.TextureMetadata[index].Format = tex.Format
sc.optimizedScene.TextureMetadata[index].Width = tex.Width
sc.optimizedScene.TextureMetadata[index].Height = tex.Height
sc.optimizedScene.TextureMetadata[index].DataOffset = offset
var texIndex uint32 = 0
for _, tex := range sc.parsedScene.Textures {
if !tex.Used {
continue
}
sc.optimizedScene.TextureMetadata[texIndex].Format = tex.Format
sc.optimizedScene.TextureMetadata[texIndex].Width = tex.Width
sc.optimizedScene.TextureMetadata[texIndex].Height = tex.Height
sc.optimizedScene.TextureMetadata[texIndex].DataOffset = offset

// Copy data
copy(sc.optimizedScene.TextureData[offset:], tex.Data)
offset += uint32(align4(len(tex.Data)))

// Set TexIndex in the parsed texture so it can be used by the material compiler
tex.TexIndex = texIndex
texIndex++
}

sc.logger.Noticef("processed textures in %d ms", time.Since(start).Nanoseconds()/1e6)
sc.logger.Noticef("processed textures in %d ms (pruned: %d)", time.Since(start).Nanoseconds()/1e6, len(sc.parsedScene.Textures)-activeTextures)
return nil
}

Expand Down Expand Up @@ -163,7 +194,7 @@ func (sc *sceneCompiler) partitionGeometry() error {
sc.optimizedScene.UvList[vertexOffset+2] = prim.UVs[2]

// Lookup root material node for primitive material index
matNodeIndex := sc.optimizedScene.MaterialNodeRoots[prim.MaterialIndex]
matNodeIndex := sc.optimizedScene.MaterialNodeRoots[prim.Material.MatIndex]
sc.optimizedScene.MaterialIndex[primOffset] = matNodeIndex

// Check if this an emissive primitive and keep track of it
Expand Down Expand Up @@ -275,14 +306,27 @@ func (sc *sceneCompiler) createLayeredMaterialTrees() error {
start := time.Now()
sc.logger.Noticef("processing %d materials", len(sc.parsedScene.Materials))

activeMaterials := 0
for _, mat := range sc.parsedScene.Materials {
if !mat.Used {
continue
}
activeMaterials++
}

sc.optimizedScene.MaterialNodeList = make([]scene.MaterialNode, 0)
sc.optimizedScene.MaterialNodeRoots = make([]uint32, len(sc.parsedScene.Materials))
sc.optimizedScene.MaterialNodeRoots = make([]uint32, activeMaterials)

var rootNodeIndex uint32 = 0
for matIndex, mat := range sc.parsedScene.Materials {
var matIndex uint32
for _, mat := range sc.parsedScene.Materials {
if !mat.Used {
sc.logger.Infof(`skipping unused material "%s"`, mat.Name)
continue
}
sc.logger.Infof(`processing material "%s"`, mat.Name)

// Till we get material expressions working use a naive approach to building material nodes
// If no material expression is defined make a best-effort attempt to build one
isDiffuse := mat.IsDiffuse()
isSpecularReflection := mat.IsSpecularReflection()
isSpecularTransmission := mat.IsSpecularTransmission()
Expand Down Expand Up @@ -333,9 +377,13 @@ func (sc *sceneCompiler) createLayeredMaterialTrees() error {
if mat.Name == sceneEmissiveMaterialName {
sc.optimizedScene.SceneEmissiveMatIndex = sc.findMaterialNodeByBxdf(rootNodeIndex, scene.Emissive)
}

// Set MatIndex in parsed material so it can be used by the geometry compiler
mat.MatIndex = matIndex
matIndex++
}

sc.logger.Noticef("processed materials in %d ms", time.Since(start).Nanoseconds()/1e6)
sc.logger.Noticef("processed materials in %d ms (pruned: %d)", time.Since(start).Nanoseconds()/1e6, len(sc.parsedScene.Materials)-activeMaterials)
return nil
}

Expand Down
24 changes: 18 additions & 6 deletions scene/optimized_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,12 @@ func (m *MaterialNode) SetBlendFunc(blendfunc MatNodeBlendFunc) {
}

// Set Kval tex index.
func (m *MaterialNode) SetKvalTex(texIndex int32) {
m.UnionData[0] = texIndex
func (m *MaterialNode) SetKvalTex(tex *ParsedTexture) {
if tex == nil {
m.UnionData[0] = -1
return
}
m.UnionData[0] = int32(tex.TexIndex)
}

// Get Kval tex index.
Expand All @@ -185,13 +189,21 @@ func (m *MaterialNode) GetKvalTex() int32 {
}

// Set normal tex index.
func (m *MaterialNode) SetNormalTex(texIndex int32) {
m.UnionData[1] = texIndex
func (m *MaterialNode) SetNormalTex(tex *ParsedTexture) {
if tex == nil {
m.UnionData[1] = -1
return
}
m.UnionData[1] = int32(tex.TexIndex)
}

// Set Nval tex index.
func (m *MaterialNode) SetNvalTex(texIndex int32) {
m.UnionData[2] = texIndex
func (m *MaterialNode) SetNvalTex(tex *ParsedTexture) {
if tex == nil {
m.UnionData[2] = -1
return
}
m.UnionData[2] = int32(tex.TexIndex)
}

// Set leaf BxDF type.
Expand Down
75 changes: 52 additions & 23 deletions scene/parsed_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

// The primitive struct represents a parsed triangle primitive.
type ParsedPrimitive struct {
Vertices [3]types.Vec3
Normals [3]types.Vec3
UVs [3]types.Vec2
MaterialIndex uint32
Vertices [3]types.Vec3
Normals [3]types.Vec3
UVs [3]types.Vec2
Material *ParsedMaterial

bbox [2]types.Vec3
center types.Vec3
Expand Down Expand Up @@ -130,36 +130,68 @@ type ParsedMaterial struct {
Nr float32

// Textures for modulating above parameters.
KdTex int32
KsTex int32
KeTex int32
TfTex int32
NormalTex int32
NiTex int32
NrTex int32
KdTex *ParsedTexture
KsTex *ParsedTexture
KeTex *ParsedTexture
TfTex *ParsedTexture
NormalTex *ParsedTexture
NiTex *ParsedTexture
NrTex *ParsedTexture

// Layered material expression.
MaterialExpression string

// Flag to quickly check if any primitive references this material.
Used bool

// The material slice index allocated by the compiler for this material.
MatIndex uint32
}

// Flag material textures as used
func (pm *ParsedMaterial) MarkTexturesAsUsed() {
pm.Used = true
if pm.KdTex != nil {
pm.KdTex.Used = true
}
if pm.KsTex != nil {
pm.KsTex.Used = true
}
if pm.KeTex != nil {
pm.KeTex.Used = true
}
if pm.TfTex != nil {
pm.TfTex.Used = true
}
if pm.NormalTex != nil {
pm.NormalTex.Used = true
}
if pm.NiTex != nil {
pm.NiTex.Used = true
}
if pm.NrTex != nil {
pm.NrTex.Used = true
}
}

// Return true if material contains a diffuse component.
func (pm *ParsedMaterial) IsDiffuse() bool {
return pm.Kd.Len() > 0 || pm.KdTex != -1
return pm.Kd.Len() > 0 || pm.KdTex != nil
}

// Return true if material contains a specular component.
func (pm *ParsedMaterial) IsSpecularReflection() bool {
return pm.Ks.Len() > 0 || pm.KsTex != -1
return pm.Ks.Len() > 0 || pm.KsTex != nil
}

// Return true if material contains an emissive component.
func (pm *ParsedMaterial) IsEmissive() bool {
return pm.Ke.Len() > 0 || pm.KeTex != -1
return pm.Ke.Len() > 0 || pm.KeTex != nil
}

// Return true if material is refractive.
func (pm *ParsedMaterial) IsSpecularTransmission() bool {
return pm.Ni != 0 || pm.NiTex != -1
return pm.Ni != 0 || pm.NiTex != nil
}

// A texture image and its metadata.
Expand All @@ -170,6 +202,11 @@ type ParsedTexture struct {
Height uint32

Data []byte

Used bool

// The texture slice index allocated by the compiler for this material.
TexIndex uint32
}

// Camera settings
Expand Down Expand Up @@ -218,13 +255,5 @@ func NewParsedMesh(name string) *ParsedMesh {
func NewParsedMaterial(name string) *ParsedMaterial {
return &ParsedMaterial{
Name: name,
// Disable textures,
KdTex: -1,
KsTex: -1,
KeTex: -1,
TfTex: -1,
NormalTex: -1,
NiTex: -1,
NrTex: -1,
}
}
Loading

0 comments on commit 76d9467

Please sign in to comment.