File: System\Windows\Media\Effects\PixelShader.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//  Microsoft Windows Presentation Foundation
//
 
using System;
using System.IO;
using MS.Internal;
using MS.Win32.PresentationCore;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Security;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Composition;
using System.Windows;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Markup;
using SR=MS.Internal.PresentationCore.SR;
using System.Windows.Navigation;
using System.IO.Packaging;
using MS.Internal.PresentationCore; 
 
using PackUriHelper = System.IO.Packaging.PackUriHelper;
 
namespace System.Windows.Media.Effects
{
    public sealed partial class PixelShader : Animatable, DUCE.IResource
    {
        // Method and not property so we don't need to hang onto the stream.
        public void SetStreamSource(Stream source)
        {
            WritePreamble();
            LoadPixelShaderFromStreamIntoMemory(source);
            WritePostscript();
        }
 
        /// <summary>
        /// The major version of the pixel shader
        /// </summary>
        internal short ShaderMajorVersion
        {
            get;
            private set;
        }
 
        /// <summary>
        /// The minor version of the pixel shader
        /// </summary>
        internal short ShaderMinorVersion
        {
            get;
            private set;
        }
 
        /// <summary>
        /// If any PixelShader cannot be processed by the render thread, this
        /// event will be raised.  
        /// </summary>
        public static event EventHandler InvalidPixelShaderEncountered
        {
            add
            {
                // Just forward to the internal event on MediaContext
                MediaContext mediaContext = MediaContext.CurrentMediaContext;
                mediaContext.InvalidPixelShaderEncountered += value;
            }
            remove
            {
                MediaContext mediaContext = MediaContext.CurrentMediaContext;
                mediaContext.InvalidPixelShaderEncountered -= value;
            }
        }
 
        /// <summary>
        /// This method is invoked whenever the source property changes. 
        /// </summary>
        private void UriSourcePropertyChangedHook(DependencyPropertyChangedEventArgs e)
        {
            // Decided against comparing the URI because the user might want to change the shader on the filesystem
            // and reload it. 
 
            // We do not support async loading of shaders here. If that is desired the user needs to use the SetStreamSource
            // API.
            
            Uri newUri = (Uri)e.NewValue;
            Stream stream = null;
 
            try {                    
                if (newUri != null)
                {
                    if (!newUri.IsAbsoluteUri)
                    {
                         newUri = BaseUriHelper.GetResolvedUri(BaseUriHelper.BaseUri, newUri);
                    }
 
                    Debug.Assert(newUri.IsAbsoluteUri);
 
                    // Now the URI is an absolute URI.
 
                    //
                    // Only allow file and pack URIs.
                    if (!newUri.IsFile && 
                        !MS.Internal.IO.Packaging.PackUriHelper.IsPackUri(newUri))
                    {
                        throw new ArgumentException(SR.Effect_SourceUriMustBeFileOrPack);
                    }
 
                    stream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(newUri);
                }
 
                LoadPixelShaderFromStreamIntoMemory(stream);            
            }
            finally
            {
                if (stream != null)
                {
                    stream.Dispose();
                }
            }
        }
 
        /// <summary>
        /// Reads the byte code for the pixel shader into a local byte array. If the stream is null, the byte array
        /// will be empty (length 0). The compositor will use an identity shader.
        /// </summary>
        private void LoadPixelShaderFromStreamIntoMemory(Stream source) 
        {
            _shaderBytecode = null;
 
            if (source != null)
            {
                if (!source.CanSeek)
                {
                    throw new InvalidOperationException(SR.Effect_ShaderSeekableStream);
                }
 
                int len = (int)source.Length;  // only works on seekable streams.
 
                if (len % sizeof(int) != 0)
                {
                    throw new InvalidOperationException(SR.Effect_ShaderBytecodeSize);
                }
                
                BinaryReader br = new BinaryReader(source);
                _shaderBytecode = new byte[len];
                int lengthRead = br.Read(_shaderBytecode, 0, (int)len);
 
                //
                // The first 4 bytes contain version info in the form of
                // [Minor][Major][xx][xx]
                //
                if (_shaderBytecode != null && _shaderBytecode.Length > 3)
                {
                    ShaderMajorVersion = _shaderBytecode[1];
                    ShaderMinorVersion = _shaderBytecode[0];
                }
                else
                {
                    ShaderMajorVersion = ShaderMinorVersion = 0;
                }
 
                Debug.Assert(len == lengthRead); 
            }
 
            // We received new stream data. Need to register for a async update to update the composition
            // engine. 
            RegisterForAsyncUpdateResource();
 
            //
            // Notify any ShaderEffects listening that the bytecode changed.
            // The bytecode may have changed from a ps_3_0 shader to a ps_2_0 shader, and any
            // ShaderEffects using this PixelShader need to check that they are using only
            // registers that are valid in ps_2_0.
            //
            if (_shaderBytecodeChanged != null)
            {
                _shaderBytecodeChanged(this, null);
            }
        }
 
 
        private void ManualUpdateResource(DUCE.Channel channel, bool skipOnChannelCheck)
        {
            // If we're told we can skip the channel check, then we must be on channel
            Debug.Assert(!skipOnChannelCheck || _duceResource.IsOnChannel(channel));
 
            if (skipOnChannelCheck || _duceResource.IsOnChannel(channel))
            {
                checked
                {
                    DUCE.MILCMD_PIXELSHADER data;
                    data.Type = MILCMD.MilCmdPixelShader;
                    data.Handle = _duceResource.GetHandle(channel);
                    data.PixelShaderBytecodeSize = (uint)(_shaderBytecode?.Length ?? 0);
                    data.ShaderRenderMode = ShaderRenderMode;
                    data.CompileSoftwareShader = CompositionResourceManager.BooleanToUInt32(!(ShaderMajorVersion == 3 && ShaderMinorVersion == 0));
 
                    unsafe
                    {
                        channel.BeginCommand(
                            (byte*)&data,
                            sizeof(DUCE.MILCMD_PIXELSHADER),
                            (int)data.PixelShaderBytecodeSize);   // extra data
 
                            if (data.PixelShaderBytecodeSize > 0)
                            {
                                fixed (byte *pPixelShaderBytecode = _shaderBytecode)
                                {
                                    channel.AppendCommandData(pPixelShaderBytecode, (int)data.PixelShaderBytecodeSize);
                                }
                            }
                    }
                }
 
                channel.EndCommand();
            }
        }
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCore(Freezable)">Freezable.CloneCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void CloneCore(Freezable sourceFreezable)
        {
            PixelShader shader = (PixelShader)sourceFreezable;
            base.CloneCore(sourceFreezable);
            CopyCommon(shader);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCurrentValueCore(Freezable)">Freezable.CloneCurrentValueCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void CloneCurrentValueCore(Freezable sourceFreezable)
        {
            PixelShader shader = (PixelShader)sourceFreezable;
            base.CloneCurrentValueCore(sourceFreezable);
            CopyCommon(shader);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetAsFrozenCore(Freezable)">Freezable.GetAsFrozenCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void GetAsFrozenCore(Freezable sourceFreezable)
        {
            PixelShader shader = (PixelShader)sourceFreezable;
            base.GetAsFrozenCore(sourceFreezable);
            CopyCommon(shader);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetCurrentValueAsFrozenCore(Freezable)">Freezable.GetCurrentValueAsFrozenCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void GetCurrentValueAsFrozenCore(Freezable sourceFreezable)
        {
            PixelShader shader = (PixelShader)sourceFreezable;
            base.GetCurrentValueAsFrozenCore(sourceFreezable);
            CopyCommon(shader);
        }
 
        /// <summary>
        /// Clones values that do not have corresponding DPs.
        /// </summary>
        /// <param name="transform"></param>
        private void CopyCommon(PixelShader shader)
        {
            byte[] sourceBytecode = shader._shaderBytecode;
            byte[] destinationBytecode = null;
 
            if (sourceBytecode != null)
            {
                destinationBytecode = new byte[sourceBytecode.Length];
                sourceBytecode.CopyTo(destinationBytecode, 0);
            }
 
            _shaderBytecode = destinationBytecode;
        }
 
        private byte[] _shaderBytecode;
 
        //
        // Introduced with ps_3_0 for ShaderEffect's use
        //
        // The scenario is setting up a ShaderEffect with a PixelShader containing a ps_3_0 shader,
        // setting a ps_3_0 only register (int, bool, or float registers 32 and above), then using
        // ShaderEffect.PixelShader.UriSource (or SetStreamSource) to swap the ps_3_0 shader out
        // for a ps_2_0 one. Now there's a value in a register that doesn't exist in ps_2_0.
        //
        // The fix is to have ShaderEffect listen for this event on its PixelShader, and verifying
        // that no invalid registers are used when switching a ps_3_0 register to a ps_2_0 one.
        //
        internal event EventHandler _shaderBytecodeChanged;
    }
}