Pergunta

I would like to do the following :

(project is a User Control library for WPF)

  • add a bunch of .FX (shader source code) files to the project as resources (Build action)
  • transform each to a .PS file (compiled shader) by invoking FXC.EXE utility
  • use the resulting file in place of the inputted file

I have been looking to write a CustomTool, unfortunately the tool is never seen by Visual Studio as it's mentioned in the article. In the article it is said that sometimes it is not seen but in my case it translates to every time.

I also looked at MSBuild Transforms but I'm not really sure if it would be appropriate for the task.

The goal of this is to include shader files source code and transform them at build time instead of manually building them from command line and dropping them every time to the project.

Do you know how one can achieve this ? Any methods are welcome

EDIT

Answer thanks to @Luaan :

public class CompileEffectTask : Task
{
    public string[] Files { get; set; }

    public override bool Execute()
    {
        if (Files != null)
        {
            foreach (string file in Files)
            {
                if (file != null)
                {
                    Log.LogMessage(MessageImportance.High, file);

                    string s = @"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\fxc.exe";
                    string changeExtension = Path.ChangeExtension(file, "ps");
                    string arguments = string.Format("/T ps_3_0 /Fo \"{0}\"" + " " + "\"{1}\"", changeExtension,
                        file);
                    Log.LogMessage(MessageImportance.High, arguments);
                    var process = new Process
                    {
                        StartInfo = new ProcessStartInfo(s, arguments)
                    };
                    process.Start();
                    process.WaitForExit();
                }
            }
        }
        return true;
    }
}

And the MSBuild part :

  <UsingTask TaskName="CompileEffectTask" AssemblyFile="D:\HLSLCompiler.dll" />
  <PropertyGroup>
    <BuildDependsOn>
      MyCustomTarget1;
      $(BuildDependsOn);
    </BuildDependsOn>
  </PropertyGroup>
  <Target Name="MyCustomTarget1">
    <Message Text="CompileEffectTask started" Importance="high" />
    <Message Text="Compiling FX files ..." Importance="high" />
    <CompileEffectTask Files="@(CompileEffectTask)"/>
    <Message Text="Adding resulting .PS files as resources ..." Importance="high" />
    <ItemGroup>
      <Resource Include="**\*.ps" />
    </ItemGroup>
  </Target>
  <Target Name="AfterBuild">
    <CreateItem Include="**\*.ps">
      <Output TaskParameter="Include" ItemName="DeleteAfterBuild" />
    </CreateItem>
    <Delete Files="@(DeleteAfterBuild)" />
  </Target>

(still needs some cleaning but it works :D)

Foi útil?

Solução

Custom tools do work, in fact, but they're rather tricky to setup - they're COM extensions to Visual Studio. However, the better solution for your case would be a custom build target or a pre-build event anyway - custom tools (code generators) are better suited for generating code (text) rather than binary files.

So, the pre-build event is the simple one. It's just some script that's run before the project starts building. You can find it in project properties. The simplest way would be to have all your .fx files in one directory, and in the pre-build event, you'd just call fxc.exe on each of them.

Now, build targets are cooler. They allow you to add your own build actions to files, among other things. So you'd just select CompileEffect in Build action of your files, and magic happens.

The target file can be quite simple:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <AvailableItemName Include="CompileEffect"></AvailableItemName>
  </ItemGroup>
</Project>

Or you can just put the ItemGroup part inside of your project file directly (otherwise you'd want to include this target file).

Next, you want to set the task as part of your build:

<PropertyGroup>
  <BuildDependsOn>
    MyCompileTarget;
    $(BuildDependsOn);
  </BuildDependsOn>
</PropertyGroup>

This basically says "run my build target first, and after that whatever you'd want".

Now, for the building:

<Target Name="MyCompileTarget">
  <CompileEffectTask
      ProjectDirectory="$(ProjectDir)"
      Files="@(CompileEffect)"
      RootNamespace="$(RootNamespace)">
  </CompileEffectTaskTask>
</Target>

How does Visual Studio know what CompileEffectTask is?

<UsingTask TaskName="MyAssembly.CompileEffectTask" 
           AssemblyFile="C:\MyAssembly.dll"/>

And then you just need to implement the compiler task itself.

Now, if you only want to call an executable or a batch script, you don't even need that custom task, because there's a lot of built-in tasks in MSBuild (and even more in MSBuild Community Tasks). Exec task should work:

<Target Name="MyCompileTarget">
  <Exec Command="fxc.exe @(CompileEffect)" />
</Target>

You might have to write a for cycle there, I'm not entirely sure. There's a lot of things you can do to customize project builds, http://msdn.microsoft.com/en-us/library/0k6kkbsd.aspx (especially the Task refecence part) is a rather good start.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top