int ChoiceForm::AutotuneHelper(HashSet<String> & selectedChoices, EnumerableDictionary<String, Spire::Compiler::ShaderChoiceValue>& currentChoices, float timeBudget, bool countOnly) { auto choices = choiceControl->GetChoices(currentShaderName, currentChoices); List<Spire::Compiler::ShaderChoice> filteredChoices; for (auto & choice : choices) if (!currentChoices.ContainsKey(choice.ChoiceName)) filteredChoices.Add(choice); choices = _Move(filteredChoices); // find first non-trivial choice Spire::Compiler::ShaderChoice * currentChoice = nullptr; for (auto & choice : choices) { if (choice.Options.Count() > 1 && selectedChoices.Contains(choice.ChoiceName)) { currentChoice = &choice; break; } } if (currentChoice) { int count = 0; for (auto & opt : currentChoice->Options) { EnumerableDictionary<String, Spire::Compiler::ShaderChoiceValue> newChoices = currentChoices; newChoices[currentChoice->ChoiceName] = opt; count += AutotuneHelper(selectedChoices, newChoices, timeBudget, countOnly); } return count; } else { if (!countOnly) { printf("Testing shader variant..."); // compile and evaluate auto schedule = GenerateSchedule(currentChoices, EnumerableDictionary<String, EnumerableDictionary<String, String>>()); choiceControl->RecompileShader(currentShaderName, schedule); // render 1000 frames and measure time float minTime = 1e30f; for (int f = 0; f < 20; f++) { glFinish(); auto timeP = CoreLib::Diagnostics::PerformanceCounter::Start(); for (int i = 0; i < 10; i++) { choiceControl->RenderFrame(); } glFinish(); auto time = (float)CoreLib::Diagnostics::PerformanceCounter::ToSeconds(CoreLib::Diagnostics::PerformanceCounter::End(timeP)) * 100.0f; if (time < minTime) minTime = time; } choiceControl->UpdateWindow(); auto frameData = ReadFrameData(choiceControl->RenderFrame()); float error = MeasureError(referenceFrame, frameData); float value = 0; if (minTime < timeBudget) value = error; else value = 20.0f + minTime; if (value < currentBestValue) { currentBestValue = value; currentBestChoices = currentChoices; } autotuningLog << minTime << L"," << error<<EndLine; printf("%f ms, error: %f\n", minTime, error); } return 1; } }
/* Generate a shader variant by applying mechanic choice rules and the choice file. The choice file provides "preferred" definitions, as represented in ShaderComponentSymbol::Type::PinnedWorlds The process resolves the component references by picking a pinned definition if one is available, or a definition with the preferred import path as defined by import operator ordering. After all references are resolved, all unreferenced definitions (dead code) are eliminated, resulting a shader variant ready for code generation. */ RefPtr<ShaderIR> GenerateShaderVariantIR(CompileResult & cresult, ShaderClosure * shader, Schedule & schedule, SymbolTable * symbolTable) { RefPtr<ShaderIR> result = new ShaderIR(); result->Shader = shader; result->SymbolTable = symbolTable; // mark pinned worlds for (auto & comp : shader->Components) { for (auto & impl : comp.Value->Implementations) { for (auto & w : impl->Worlds) { if (impl->SrcPinnedWorlds.Contains(w) || impl->SyntaxNode->IsInline() || impl->ExportWorlds.Contains(w) || impl->SyntaxNode->IsInput()) { comp.Value->Type->PinnedWorlds.Add(w); } } } } // apply choices Dictionary<String, ShaderComponentSymbol*> choiceComps; for (auto & comp : shader->AllComponents) { for (auto & choiceName : comp.Value.Symbol->ChoiceNames) choiceComps[choiceName] = comp.Value.Symbol; } HashSet<ShaderComponentImplSymbol*> pinnedImpl; for (auto & choice : schedule.Choices) { ShaderComponentSymbol * comp = nullptr; if (choiceComps.TryGetValue(choice.Key, comp)) { comp->Type->PinnedWorlds.Clear(); for (auto & selectedDef : choice.Value) { if (comp->Type->ConstrainedWorlds.Contains(selectedDef->WorldName)) { comp->Type->PinnedWorlds.Add(selectedDef->WorldName); // find specified impl for (auto & impl : comp->Implementations) { if (impl->Worlds.Contains(selectedDef->WorldName)) pinnedImpl.Add(impl.Ptr()); } } else { cresult.GetErrorWriter()->diagnose(selectedDef.Ptr()->Position, Diagnostics::worldIsNotAValidChoiceForKey, selectedDef->WorldName, choice.Key); } } } } for (auto & attribs : schedule.AddtionalAttributes) { ShaderComponentSymbol * comp = nullptr; if (choiceComps.TryGetValue(attribs.Key, comp)) { // apply attributes for (auto & impl : comp->Implementations) { for (auto & attrib : attribs.Value) { auto modifier = new SimpleAttribute(); modifier->Key = attrib.Key; modifier->Value.Content = attrib.Value; modifier->next = impl->SyntaxNode->modifiers.first; impl->SyntaxNode->modifiers.first = modifier; } } } } // generate definitions Dictionary<ShaderClosure*, ModuleInstanceIR*> moduleInstanceMap; auto createModuleInstance = [&](ShaderClosure * closure) { ModuleInstanceIR * inst; if (moduleInstanceMap.TryGetValue(closure, inst)) return inst; List<String> namePath; auto parent = closure; while (parent) { if (parent->Name.Length()) namePath.Add(parent->Name); else namePath.Add(parent->ModuleSyntaxNode->Name.Content); parent = parent->Parent; } StringBuilder sbBindingName; for (int i = namePath.Count() - 2; i >= 0; i--) { sbBindingName << namePath[i]; if (i > 0) sbBindingName << "."; } // if this is the root module, its binding name is module name. if (namePath.Count() == 1) sbBindingName << namePath[0]; inst = new ModuleInstanceIR(); inst->SyntaxNode = closure->ModuleSyntaxNode; inst->BindingIndex = closure->BindingIndex; inst->UsingPosition = closure->UsingPosition; result->ModuleInstances.Add(inst); inst->BindingName = sbBindingName.ProduceString(); moduleInstanceMap[closure] = inst; return inst; }; for (auto & comp : shader->AllComponents) { EnumerableDictionary<String, ComponentDefinitionIR*> defs; Dictionary<String, ShaderComponentImplSymbol*> impls; for (auto & impl : comp.Value.Symbol->Implementations) { auto createComponentDef = [&](const String & w) { RefPtr<ComponentDefinitionIR> def = new ComponentDefinitionIR(); def->OriginalName = comp.Value.Symbol->Name; def->UniqueKey = comp.Value.Symbol->UniqueKey; def->UniqueName = comp.Value.Symbol->UniqueName; def->Type = comp.Value.Symbol->Type->DataType; def->IsEntryPoint = (impl->ExportWorlds.Contains(w) || impl->SyntaxNode->IsParam() || (shader->Pipeline->IsAbstractWorld(w) && (impl->SyntaxNode->HasSimpleAttribute("Pinned") || shader->Pipeline->Worlds[w]()->HasSimpleAttribute("Pinned")))); CloneContext cloneCtx; def->SyntaxNode = impl->SyntaxNode->Clone(cloneCtx); def->World = w; def->ModuleInstance = createModuleInstance(comp.Value.Closure); return def; }; // parameter component will only have one defintion that is shared by all worlds if (impl->SyntaxNode->IsParam()) { auto def = createComponentDef("<uniform>"); result->Definitions.Add(def); defs["<uniform>"] = def.Ptr(); } else { for (auto & w : impl->Worlds) { auto def = createComponentDef(w); result->Definitions.Add(def); bool existingDefIsPinned = false; if (defs.ContainsKey(w)) existingDefIsPinned = pinnedImpl.Contains(impls[w]()); if (!existingDefIsPinned) { defs[w] = def.Ptr(); impls[w] = impl.Ptr(); } } } } result->DefinitionsByComponent[comp.Key] = defs; } bool changed = true; while (changed) { changed = false; result->ResolveComponentReference(); result->EliminateDeadCode(); // check circular references for (auto & def : result->Definitions) { if (def->Dependency.Contains(def.Ptr())) { cresult.GetErrorWriter()->diagnose(def->SyntaxNode->Position, Diagnostics::componentDefinitionCircularity, def->OriginalName); return nullptr; } } /* // eliminate redundant (downstream) definitions, one at a time auto comps = result->GetComponentDependencyOrder(); for (int i = comps.Count() - 1; i >= 0; i--) { auto comp = comps[i]; auto & defs = result->DefinitionsByComponent[comp->UniqueName](); EnumerableHashSet<ComponentDefinitionIR*> removedDefs; for (auto & def : defs) if (!def.Value->IsEntryPoint && !comp->Type->PinnedWorlds.Contains(def.Value->World)) { for (auto & otherDef : defs) { if (otherDef.Value != def.Value && !removedDefs.Contains(otherDef.Value) && shader->Pipeline->IsWorldReachable(otherDef.Value->World, def.Value->World)) { removedDefs.Add(def.Value); break; } } } if (removedDefs.Count()) { result->RemoveDefinitions([&](ComponentDefinitionIR* def) {return removedDefs.Contains(def); }); changed = true; } } */ } return result; }