diff options
Diffstat (limited to 'Runtime/Shaders')
26 files changed, 5963 insertions, 0 deletions
diff --git a/Runtime/Shaders/BufferedVBO.cpp b/Runtime/Shaders/BufferedVBO.cpp new file mode 100644 index 0000000..452d993 --- /dev/null +++ b/Runtime/Shaders/BufferedVBO.cpp @@ -0,0 +1,142 @@ +#include "UnityPrefix.h" +#include "BufferedVBO.h" + + +BufferedVBO::BufferedVBO() +{ + m_AllocatedSize = 0; +} + +BufferedVBO::~BufferedVBO() +{ + UnbufferVertexData(); +} + +void BufferedVBO::BufferAllVertexData( const VertexBufferData& buffer ) +{ + bool copyModes[kStreamModeCount]; + std::fill(copyModes, copyModes + kStreamModeCount, true); + BufferVertexData(buffer, copyModes); +} + +void BufferedVBO::BufferAccessibleVertexData( const VertexBufferData& buffer ) +{ + bool copyModes[kStreamModeCount]; + std::fill(copyModes, copyModes + kStreamModeCount, true); + copyModes[kStreamModeNoAccess] = false; + BufferVertexData(buffer, copyModes); +} + +void BufferedVBO::BufferVertexData( const VertexBufferData& buffer, bool copyModes[kStreamModeCount] ) +{ + std::copy(buffer.channels, buffer.channels + kShaderChannelCount, m_VertexData.channels); + + int streamOffset = 0; + for (int s = 0; s < kMaxVertexStreams; s++) + { + StreamInfo& stream = m_VertexData.streams[s]; + const StreamInfo& srcStream = buffer.streams[s]; + UInt8 mode = m_StreamModes[s]; + Assert(mode < kStreamModeCount); + if (copyModes[mode] && srcStream.channelMask) + { + stream = srcStream; + stream.offset = streamOffset; + streamOffset += CalculateVertexStreamSize(buffer, s); + streamOffset = VertexData::AlignStreamSize(streamOffset); + } + else + stream.Reset(); + } + size_t allocSize = VertexData::GetAllocateDataSize(streamOffset); + if (allocSize != m_AllocatedSize) + { + UnbufferVertexData(); + m_AllocatedSize = allocSize; + m_VertexData.buffer = (UInt8*)UNITY_MALLOC_ALIGNED (kMemVertexData, allocSize, VertexData::kVertexDataAlign); + } + m_VertexData.bufferSize = streamOffset; + m_VertexData.vertexCount = buffer.vertexCount; + + for (int s = 0; s < kMaxVertexStreams; s++) + { + const StreamInfo& srcStream = buffer.streams[s]; + UInt8 mode = m_StreamModes[s]; + if (copyModes[mode] && srcStream.channelMask) + CopyVertexStream(buffer, GetStreamBuffer(s), s); + } +} + +void BufferedVBO::UnbufferVertexData() +{ + if (m_AllocatedSize > 0) + UNITY_FREE(kMemVertexData, m_VertexData.buffer); + + m_AllocatedSize = 0; + m_VertexData.buffer = NULL; +} + +bool BufferedVBO::MapVertexStream( VertexStreamData& outData, unsigned stream ) +{ + Assert(!m_IsStreamMapped[stream]); + DebugAssert(m_StreamModes[stream] != kStreamModeNoAccess); + if (m_StreamModes[stream] == kStreamModeNoAccess) + return false; + DebugAssert(m_VertexData.buffer); + if (!m_VertexData.buffer) + return false; + outData.buffer = m_VertexData.buffer + m_VertexData.streams[stream].offset; + outData.channelMask = m_VertexData.streams[stream].channelMask; + outData.stride = m_VertexData.streams[stream].stride; + outData.vertexCount = m_VertexData.vertexCount; + m_IsStreamMapped[stream] = true; + return true; +} + +void BufferedVBO::UnmapVertexStream( unsigned stream ) +{ + Assert(m_IsStreamMapped[stream]); + m_IsStreamMapped[stream] = false; +} + +int BufferedVBO::GetRuntimeMemorySize() const +{ + return m_AllocatedSize; +} + +UInt8* BufferedVBO::GetStreamBuffer(unsigned stream) +{ + return m_VertexData.buffer + m_VertexData.streams[stream].offset; +} + +UInt8* BufferedVBO::GetChannelDataAndStride(ShaderChannel channel, UInt32& outStride) +{ + const ChannelInfo& info = m_VertexData.channels[channel]; + if (info.IsValid()) + { + outStride = info.CalcStride(m_VertexData.streams); + return m_VertexData.buffer + info.CalcOffset(m_VertexData.streams); + } + outStride = 0; + return NULL; +} + +void BufferedVBO::GetChannelDataAndStrides(void* channelData[kShaderChannelCount], UInt32 outStrides[kShaderChannelCount]) +{ + for (int i = 0; i < kShaderChannelCount; i++) + channelData[i] = GetChannelDataAndStride(ShaderChannel(i), outStrides[i]); +} + +void BufferedVBO::GetChannelOffsetsAndStrides(void* channelOffsets[kShaderChannelCount], UInt32 outStrides[kShaderChannelCount]) +{ + for (int i = 0; i < kShaderChannelCount; i++) + { + channelOffsets[i] = reinterpret_cast<void*>(m_VertexData.channels[i].CalcOffset(m_VertexData.streams)); + outStrides[i] = m_VertexData.channels[i].CalcStride(m_VertexData.streams); + } +} + +void BufferedVBO::UnloadSourceVertices() +{ + UnbufferVertexData(); +} diff --git a/Runtime/Shaders/BufferedVBO.h b/Runtime/Shaders/BufferedVBO.h new file mode 100644 index 0000000..5932b28 --- /dev/null +++ b/Runtime/Shaders/BufferedVBO.h @@ -0,0 +1,35 @@ +#ifndef BUFFEREDVBO_H +#define BUFFEREDVBO_H + +#include "VBO.h" +#include "Runtime/Utilities/dynamic_array.h" + +class BufferedVBO : public VBO +{ +public: + BufferedVBO(); + ~BufferedVBO(); + + virtual bool MapVertexStream( VertexStreamData& outData, unsigned stream ); + virtual void UnmapVertexStream( unsigned stream ); + + virtual int GetRuntimeMemorySize() const; + + virtual void UnloadSourceVertices(); + +protected: + void BufferAllVertexData( const VertexBufferData& buffer ); + void BufferAccessibleVertexData( const VertexBufferData& buffer ); + void BufferVertexData( const VertexBufferData& buffer, bool copyModes[kStreamModeCount] ); + void UnbufferVertexData(); + + UInt8* GetStreamBuffer(unsigned stream); + UInt8* GetChannelDataAndStride(ShaderChannel channel, UInt32& outStride); + void GetChannelDataAndStrides(void* channelData[kShaderChannelCount], UInt32 outStrides[kShaderChannelCount]); + void GetChannelOffsetsAndStrides(void* channelOffsets[kShaderChannelCount], UInt32 outStrides[kShaderChannelCount]); + + VertexBufferData m_VertexData; + size_t m_AllocatedSize; +}; + +#endif diff --git a/Runtime/Shaders/ComputeShader.cpp b/Runtime/Shaders/ComputeShader.cpp new file mode 100644 index 0000000..f5b9f40 --- /dev/null +++ b/Runtime/Shaders/ComputeShader.cpp @@ -0,0 +1,425 @@ +#include "UnityPrefix.h" +#include "ComputeShader.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Profiler/Profiler.h" + +PROFILER_INFORMATION(gDispatchComputeProfile, "Compute.Dispatch", kProfilerRender) + + +// -------------------------------------------------------------------------- + + +struct ComputeProgramStruct +{ + ComputeProgramHandle handle; + int cbBindPoints[kMaxSupportedConstantBuffers]; + int texBindPoints[kMaxSupportedComputeResources]; + TextureID textures[kMaxSupportedComputeResources]; + unsigned builtinSamplers[kMaxSupportedComputeResources]; // highest 16 bits - builtin sampler type; lowest 16 bits - bind point + int inBufBindPoints[kMaxSupportedComputeResources]; + ComputeBufferID inBuffers[kMaxSupportedComputeResources]; + UInt32 outBufBindPoints[kMaxSupportedComputeResources]; // highest bit indicates whether this is raw UAV or a texture UAV + ComputeBufferID outBuffers[kMaxSupportedComputeResources]; + TextureID outTextures[kMaxSupportedComputeResources]; +}; + + +ComputeShader::ComputeShader(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_Programs(NULL) +, m_ProgramCount(0) +, m_DataBuffer(NULL) +, m_DataBufferSize(0) +, m_CBDirty(0) +{ +} + +ComputeShader::~ComputeShader () +{ + DestroyRuntimeData (); +} + + +void ComputeShader::DestroyRuntimeData () +{ + GfxDevice& device = GetGfxDevice(); + for (int i = 0; i < m_ProgramCount; ++i) + { + device.DestroyComputeProgram (m_Programs[i].handle); + } + delete[] m_Programs; + m_Programs = NULL; + m_ProgramCount = 0; + + device.DestroyComputeConstantBuffers (m_ConstantBuffers.size(), m_CBs); + + delete[] m_DataBuffer; + m_DataBuffer = NULL; + m_DataBufferSize = 0; + m_CBDirty = 0; +} + +void ComputeShader::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + DestroyRuntimeData (); + CreateRuntimeData(); +} + +void ComputeShader::CreateRuntimeData() +{ + // create compute programs + GfxDevice& device = GetGfxDevice(); + m_ProgramCount = m_Kernels.size(); + m_Programs = new ComputeProgramStruct[m_ProgramCount]; + for (int i = 0; i < m_ProgramCount; ++i) + { + const ComputeShaderKernel& kernelData = m_Kernels[i]; + m_Programs[i].handle = device.CreateComputeProgram (kernelData.code.data(), kernelData.code.size()); + + // compute CS bind points for this kernel + memset (m_Programs[i].cbBindPoints, -1, sizeof(m_Programs[i].cbBindPoints)); + for (size_t r = 0, nr = kernelData.cbs.size(); r < nr; ++r) + { + const ComputeShaderResource& res = kernelData.cbs[r]; + for (size_t cb = 0, ncb = m_ConstantBuffers.size(); cb < ncb; ++cb) + { + if (m_ConstantBuffers[cb].name == res.name) + { + m_Programs[i].cbBindPoints[cb] = res.bindPoint; + break; + } + } + } + + for (size_t t = 0, nt = kernelData.textures.size(); t < nt; ++t) + { + m_Programs[i].texBindPoints[t] = kernelData.textures[t].bindPoint; + m_Programs[i].textures[t].m_ID = 0; + } + for (size_t s = 0, ns = kernelData.builtinSamplers.size(); s < ns; ++s) + { + m_Programs[i].builtinSamplers[s] = (kernelData.builtinSamplers[s].sampler << 16) | (kernelData.builtinSamplers[s].bindPoint); + } + for (size_t b = 0, nb = kernelData.inBuffers.size(); b < nb; ++b) + { + m_Programs[i].inBufBindPoints[b] = kernelData.inBuffers[b].bindPoint; + m_Programs[i].inBuffers[b] = ComputeBufferID(); + } + for (size_t b = 0, nb = kernelData.outBuffers.size(); b < nb; ++b) + { + m_Programs[i].outBufBindPoints[b] = kernelData.outBuffers[b].bindPoint; + m_Programs[i].outBuffers[b] = ComputeBufferID(); + m_Programs[i].outTextures[b].m_ID = 0; + } + } + + // calculate space to hold all CBs, and offsets into the buffer + m_CBDirty = 0; + m_DataBufferSize = 0; + for (int cb = 0; cb < m_ConstantBuffers.size(); ++cb) + { + m_CBSizes[cb] = m_ConstantBuffers[cb].byteSize; + m_CBOffsets[cb] = m_DataBufferSize; + m_DataBufferSize += m_ConstantBuffers[cb].byteSize; + } + m_DataBuffer = new UInt8[m_DataBufferSize]; + memset (m_DataBuffer, 0, m_DataBufferSize); + + // create constant buffers + device.CreateComputeConstantBuffers (m_ConstantBuffers.size(), m_CBSizes, m_CBs); +} + + +int ComputeShader::FindKernel (const FastPropertyName& name) const +{ + for (size_t i = 0, n = m_Kernels.size(); i < n; ++i) + { + if (m_Kernels[i].name == name) + return i; + } + return -1; +} + + +void ComputeShader::SetValueParam (const FastPropertyName& name, int byteCount, const void* data) +{ + for (size_t icb = 0, ncb = m_ConstantBuffers.size(); icb < ncb; ++icb) + { + const ComputeShaderCB& cb = m_ConstantBuffers[icb]; + for (size_t p = 0, np = cb.params.size(); p < np; ++p) + { + if (cb.params[p].name == name) + { + const int cbOffset = cb.params[p].offset; + const int cbByteSize = cb.byteSize; + if (cbOffset >= cbByteSize || cbOffset + byteCount > cbByteSize) + return; + + m_CBDirty |= (1<<icb); + memcpy (m_DataBuffer + m_CBOffsets[icb] + cbOffset, data, byteCount); + } + } + } +} + + +void ComputeShader::SetTextureParam (unsigned kernelIdx, const FastPropertyName& name, TextureID tid) +{ + if (kernelIdx >= m_ProgramCount) + return; + + const std::vector<ComputeShaderResource>& textures = m_Kernels[kernelIdx].textures; + for (size_t i = 0, n = textures.size(); i < n; ++i) + { + if (textures[i].name == name) + { + m_Programs[kernelIdx].textures[i] = tid; + break; + } + } + + const std::vector<ComputeShaderResource>& outBuffers = m_Kernels[kernelIdx].outBuffers; + for (size_t i = 0, n = outBuffers.size(); i < n; ++i) + { + if (outBuffers[i].name == name) + { + m_Programs[kernelIdx].outTextures[i] = tid; + m_Programs[kernelIdx].outBufBindPoints[i] |= 0x80000000; // set highest bit, indicating it's a texture param + break; + } + } +} + +void ComputeShader::SetBufferParam (unsigned kernelIdx, const FastPropertyName& name, ComputeBufferID handle) +{ + if (kernelIdx >= m_ProgramCount) + return; + + const std::vector<ComputeShaderResource>& inBuffers = m_Kernels[kernelIdx].inBuffers; + for (size_t i = 0, n = inBuffers.size(); i < n; ++i) + { + if (inBuffers[i].name == name) + { + m_Programs[kernelIdx].inBuffers[i] = handle; + break; + } + } + + const std::vector<ComputeShaderResource>& outBuffers = m_Kernels[kernelIdx].outBuffers; + for (size_t i = 0, n = outBuffers.size(); i < n; ++i) + { + if (outBuffers[i].name == name) + { + m_Programs[kernelIdx].outBuffers[i] = handle; + m_Programs[kernelIdx].outBufBindPoints[i] &= 0x7FFFFFFF; // clear highest bit, indicating it's a buffer param + break; + } + } +} + + +void ComputeShader::DispatchComputeShader (unsigned kernelIdx, int threadsX, int threadsY, int threadsZ) +{ + if (!gGraphicsCaps.hasComputeShader) + { + ErrorString ("Platform does not support compute shaders"); + return; + } + + if (kernelIdx >= m_ProgramCount) + return; + + GPU_AUTO_SECTION(kGPUSectionOther); + PROFILER_AUTO(gDispatchComputeProfile, this) + + GfxDevice& device = GetGfxDevice(); + const unsigned cbCount = m_ConstantBuffers.size(); + device.UpdateComputeConstantBuffers (cbCount, m_CBs, m_CBDirty, m_DataBufferSize, m_DataBuffer, m_CBSizes, m_CBOffsets, m_Programs[kernelIdx].cbBindPoints); + device.UpdateComputeResources ( + m_Kernels[kernelIdx].textures.size(), m_Programs[kernelIdx].textures, m_Programs[kernelIdx].texBindPoints, + m_Kernels[kernelIdx].builtinSamplers.size(), m_Programs[kernelIdx].builtinSamplers, + m_Kernels[kernelIdx].inBuffers.size(), m_Programs[kernelIdx].inBuffers, m_Programs[kernelIdx].inBufBindPoints, + m_Kernels[kernelIdx].outBuffers.size(), m_Programs[kernelIdx].outBuffers, m_Programs[kernelIdx].outTextures, m_Programs[kernelIdx].outBufBindPoints + ); + device.DispatchComputeProgram (m_Programs[kernelIdx].handle, threadsX, threadsY, threadsZ); + GPU_TIMESTAMP(); + + // CBs we have just used aren't dirty anymore + for (unsigned i = 0; i < cbCount; ++i) + { + if (m_Programs[kernelIdx].cbBindPoints[i] >= 0) + { + UInt32 dirtyMask = (1<<i); + m_CBDirty &= ~dirtyMask; + } + } +} + + + +template<class TransferFunc> +void ComputeShaderParam::Transfer (TransferFunc& transfer) +{ + TRANSFER(name); + TRANSFER_ENUM(type) + TRANSFER(offset); + TRANSFER(arraySize); + TRANSFER(rowCount); + TRANSFER(colCount); +} + +template<class TransferFunc> +void ComputeShaderCB::Transfer (TransferFunc& transfer) +{ + TRANSFER(name); + TRANSFER(byteSize); + TRANSFER(params); +} + +template<class TransferFunc> +void ComputeShaderResource::Transfer (TransferFunc& transfer) +{ + TRANSFER(name); + TRANSFER(bindPoint); +} + +template<class TransferFunc> +void ComputeShaderBuiltinSampler::Transfer (TransferFunc& transfer) +{ + transfer.Transfer((int&)sampler, "sampler"); + TRANSFER(bindPoint); +} + +template<class TransferFunc> +void ComputeShaderKernel::Transfer (TransferFunc& transfer) +{ + TRANSFER(name); + TRANSFER(cbs); + TRANSFER(textures); + TRANSFER(builtinSamplers); + TRANSFER(inBuffers); + TRANSFER(outBuffers); + transfer.Transfer(code, "code", kHideInEditorMask); +} + + +template<class TransferFunc> +void ComputeShader::Transfer (TransferFunc& transfer) +{ + Super::Transfer (transfer); + + transfer.Transfer (m_Kernels, "kernels"); + transfer.Transfer (m_ConstantBuffers, "constantBuffers"); + + #if UNITY_EDITOR + if (!transfer.IsSerializingForGameRelease()) + transfer.Transfer (m_Errors.GetErrors(), "errors"); + #endif +} + + +IMPLEMENT_CLASS (ComputeShader) +IMPLEMENT_OBJECT_SERIALIZE (ComputeShader) + + +// -------------------------------------------------------------------------- + + +typedef List< ListNode<ComputeBuffer> > ComputeBufferList; +static ComputeBufferList s_ComputeBuffers; + + +ComputeBuffer::ComputeBuffer (size_t count, size_t stride, UInt32 flags) +: m_Count(count) +, m_Stride(stride) +, m_Flags(flags) +, m_ComputeBuffersNode(this) +{ + s_ComputeBuffers.push_back(m_ComputeBuffersNode); + ReloadToGfxDevice(); +} + +ComputeBuffer::~ComputeBuffer () +{ + UnloadFromGfxDevice(); + m_ComputeBuffersNode.RemoveFromList(); +} + +void ComputeBuffer::UnloadFromGfxDevice() +{ + GfxDevice& device = GetGfxDevice(); + device.DestroyComputeBuffer (m_BufferHandle); + device.FreeComputeBufferID (m_BufferHandle); + m_BufferHandle.m_ID = 0; +} + +void ComputeBuffer::ReloadToGfxDevice() +{ + GfxDevice& device = GetGfxDevice(); + m_BufferHandle = device.CreateComputeBufferID(); + device.CreateComputeBuffer (m_BufferHandle, m_Count, m_Stride, m_Flags); +} + + +static size_t ValidateSizeAgainstBufferAndStride (size_t size, size_t bufferSize, size_t stride) +{ + if (0 == stride) + return 0; + + size = std::min (size, bufferSize); + size /= stride; + size *= stride; + return size; +} + +void ComputeBuffer::SetData (const void* data, size_t size) +{ + if (!data || !size || !m_BufferHandle.IsValid()) + return; + + // make sure size is not too large and multiple of stride + size = ValidateSizeAgainstBufferAndStride (size, m_Count * m_Stride, m_Stride); + GetGfxDevice().SetComputeBufferData (m_BufferHandle, data, size); +} + + +void ComputeBuffer::GetData (void* dest, size_t destSize) +{ + if (!dest || !destSize || !m_BufferHandle.IsValid()) + return; + + // make sure size is not too large and multiple of stride + destSize = ValidateSizeAgainstBufferAndStride (destSize, m_Count * m_Stride, m_Stride); + GetGfxDevice().GetComputeBufferData (m_BufferHandle, dest, destSize); +} + + + +void ComputeBuffer::CopyCount (ComputeBuffer* src, ComputeBuffer* dst, UInt32 dstOffset) +{ + if (!src || !src->m_BufferHandle.IsValid()) + return; + if (!dst || !dst->m_BufferHandle.IsValid()) + return; + UInt32 srcTypeFlags = src->m_Flags & kCBFlagTypeMask; + if (!(srcTypeFlags & kCBFlagAppend) && !(srcTypeFlags & kCBFlagCounter)) + return; + + GetGfxDevice().CopyComputeBufferCount (src->m_BufferHandle, dst->m_BufferHandle, dstOffset); +} + + +void ComputeBuffer::UnloadAllFromGfxDevice () +{ + for (ComputeBufferList::iterator i = s_ComputeBuffers.begin(); i != s_ComputeBuffers.end(); ++i) + (**i).UnloadFromGfxDevice(); +} + +void ComputeBuffer::ReloadAllToGfxDevice () +{ + for (ComputeBufferList::iterator i = s_ComputeBuffers.begin(); i != s_ComputeBuffers.end(); ++i) + (**i).ReloadToGfxDevice(); +} diff --git a/Runtime/Shaders/ComputeShader.h b/Runtime/Shaders/ComputeShader.h new file mode 100644 index 0000000..9a91c8a --- /dev/null +++ b/Runtime/Shaders/ComputeShader.h @@ -0,0 +1,164 @@ +#pragma once + +#include "Runtime/BaseClasses/NamedObject.h" +#include "Runtime/GfxDevice/GfxDeviceObjects.h" +#include "External/shaderlab/Library/FastPropertyName.h" +#include "External/shaderlab/Library/ShaderLabErrors.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Utilities/NonCopyable.h" +#include <vector> + +using ShaderLab::FastPropertyName; + + +struct ComputeProgramStruct; + + +enum ComputeShaderParamType { + kCSParamFloat = 0, + kCSParamInt = 1, + kCSParamUInt = 2, + kCSParamForce32BitEnum = 0x7FFFFFFF +}; + +struct ComputeShaderParam { + DECLARE_SERIALIZE_NO_PPTR (ComputeShaderParam) + + FastPropertyName name; + ComputeShaderParamType type; + int offset; + int arraySize; + int rowCount; + int colCount; +}; + +struct ComputeShaderCB { + DECLARE_SERIALIZE_NO_PPTR (ComputeShaderCB) + + FastPropertyName name; + int byteSize; + std::vector<ComputeShaderParam> params; +}; + +struct ComputeShaderResource { + DECLARE_SERIALIZE_NO_PPTR (ComputeShaderResource) + FastPropertyName name; + int bindPoint; +}; + +struct ComputeShaderBuiltinSampler { + DECLARE_SERIALIZE_NO_PPTR (ComputeShaderBuiltinSampler) + BuiltinSamplerState sampler; + int bindPoint; +}; + +struct ComputeShaderKernel { + DECLARE_SERIALIZE_NO_PPTR (ComputeShaderKernel) + + FastPropertyName name; + std::vector<ComputeShaderResource> cbs; + // For textures, bind point is two 16 bit fields: + // lower 16 bits - texture bind point + // upper 16 bits - sampler bind point, or 0xFFFF if no sampler needed + std::vector<ComputeShaderResource> textures; + std::vector<ComputeShaderBuiltinSampler> builtinSamplers; + std::vector<ComputeShaderResource> inBuffers; + std::vector<ComputeShaderResource> outBuffers; + dynamic_array<UInt8> code; +}; + + + +class ComputeShader : public NamedObject { +public: + typedef std::vector<ComputeShaderKernel> KernelArray; + typedef std::vector<ComputeShaderCB> CBArray; + +public: + REGISTER_DERIVED_CLASS (ComputeShader, NamedObject) + DECLARE_OBJECT_SERIALIZE (ComputeShader) + + ComputeShader (MemLabelId label, ObjectCreationMode mode); + // ~ComputeShader (); declared-by-macro + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + KernelArray& GetKernels() { return m_Kernels; } + CBArray& GetConstantBuffers() { return m_ConstantBuffers; } + + #if UNITY_EDITOR + const ShaderErrors& GetErrors() const { return m_Errors; } + ShaderErrors& GetErrors() { return m_Errors; } + #endif + + int FindKernel (const FastPropertyName& name) const; + + void SetValueParam (const FastPropertyName& name, int byteCount, const void* data); + void SetTextureParam (unsigned kernelIdx, const FastPropertyName& name, TextureID tid); + void SetBufferParam (unsigned kernelIdx, const FastPropertyName& name, ComputeBufferID handle); + + void DispatchComputeShader (unsigned kernelIdx, int threadsX, int threadsY, int threadsZ); + + void UnloadFromGfxDevice() { DestroyRuntimeData(); } + void ReloadToGfxDevice() { CreateRuntimeData(); } + +private: + void DestroyRuntimeData (); + void CreateRuntimeData (); + +private: + KernelArray m_Kernels; + CBArray m_ConstantBuffers; + + ComputeProgramStruct* m_Programs; + int m_ProgramCount; + + UInt8* m_DataBuffer; + int m_DataBufferSize; + UInt32 m_CBDirty; // bit per CB + UInt32 m_CBOffsets[kMaxSupportedConstantBuffers]; // offset of each CB into data + UInt32 m_CBSizes[kMaxSupportedConstantBuffers]; + ConstantBufferHandle m_CBs[kMaxSupportedConstantBuffers]; + + #if UNITY_EDITOR + ShaderErrors m_Errors; + #endif +}; + + + +// -------------------------------------------------------------------------- + + + +class ComputeBuffer : public NonCopyable +{ +public: + ComputeBuffer (size_t count, size_t stride, UInt32 flags); + ~ComputeBuffer (); + + ComputeBufferID GetBufferHandle() const { return m_BufferHandle; } + size_t GetTotalSize() const { return m_Count * m_Stride; } + size_t GetCount() const { return m_Count; } + size_t GetStride() const { return m_Stride; } + + void SetData (const void* data, size_t size); + void GetData (void* dest, size_t destSize); + + static void CopyCount (ComputeBuffer* src, ComputeBuffer* dst, UInt32 dstOffset); + + static void UnloadAllFromGfxDevice(); + static void ReloadAllToGfxDevice(); + +private: + void UnloadFromGfxDevice(); + void ReloadToGfxDevice(); + +private: + size_t m_Count; + size_t m_Stride; + UInt32 m_Flags; + ComputeBufferID m_BufferHandle; + ListNode<ComputeBuffer> m_ComputeBuffersNode; +}; diff --git a/Runtime/Shaders/GraphicsCaps.cpp b/Runtime/Shaders/GraphicsCaps.cpp new file mode 100644 index 0000000..a2382ac --- /dev/null +++ b/Runtime/Shaders/GraphicsCaps.cpp @@ -0,0 +1,610 @@ +#include "UnityPrefix.h" +#include "GraphicsCaps.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/SystemInfo.h" + +GraphicsCaps gGraphicsCaps; + +#if UNITY_EDITOR + +#include "Runtime/Graphics/GraphicsHelper.h" +#include "Runtime/Graphics/RenderTexture.h" +#include "Runtime/Camera/Light.h" +#include "Editor/Platform/Interface/EditorWindows.h" +#include "Editor/Src/EditorUserBuildSettings.h" +#include "Editor/Src/BuildPipeline/BuildTargetPlatformSpecific.h" +#include "Runtime/Misc/PlayerSettings.h" +#include "Runtime/Modules/ExportModules.h" + +static bool gDidInitializeOriginalCaps = false; +GraphicsCaps EXPORT_COREMODULE gOriginalCaps; + +void InvalidateGraphicsStateInEditorWindows(); // EditorWindow + + +#if GFX_SUPPORTS_D3D9 +void InitializeCombinerCapsD3D9(); // CombinerD3D.cpp +#endif + +inline bool RarelySupportedFormat(RenderTextureFormat format) +{ + return (format == kRTFormatARGB4444) || (format == kRTFormatARGB1555); +} + +bool CheckEmulationValid( const GraphicsCaps& origCaps, GraphicsCaps& emulatedCaps ) +{ + if (origCaps.shaderCaps < emulatedCaps.shaderCaps) + return false; + if (!origCaps.hasFixedFunction && emulatedCaps.hasFixedFunction) + return false; + for (int i = 0; i < kRTFormatCount; ++i) + { + if (!RarelySupportedFormat((RenderTextureFormat)i)) + if (!origCaps.supportsRenderTextureFormat[i] && emulatedCaps.supportsRenderTextureFormat[i]) + return false; + } + if (origCaps.maxTexUnits < emulatedCaps.maxTexUnits) + return false; + if (!origCaps.has3DTexture && emulatedCaps.has3DTexture) + return false; + if (origCaps.npot < emulatedCaps.npot) + return false; + if (!origCaps.hasAutoMipMapGeneration && emulatedCaps.hasAutoMipMapGeneration) + return false; + if (!origCaps.hasRenderTargetStencil && emulatedCaps.hasRenderTargetStencil) + return false; + + #if GFX_SUPPORTS_OPENGL + if (GetGfxDevice().GetRenderer() == kGfxRendererOpenGL) + { + if (!origCaps.gl.hasGLSL && emulatedCaps.gl.hasGLSL) + return false; + if (!origCaps.gl.hasTextureEnvCombine3ATI && emulatedCaps.gl.hasTextureEnvCombine3ATI) + return false; + } + #endif + + return true; +} + +void GraphicsCaps::ApplyEmulationSetingsOnly( const GraphicsCaps& origCaps, const Emulation emul ) +{ + for (int q = 0; q < kTexFormatPCCount; ++q) + supportsTextureFormat[q] = true; + + bool glesTargetAndroid = GetBuildTargetGroup(GetEditorUserBuildSettings().GetActiveBuildTarget()) == kPlatformAndroid; + + switch( emul ) + { + case kEmulNone: + *this = origCaps; + break; + case kEmulSM3: + // do not set texture units; just let all SM3.0 hardware use whatever they support + shaderCaps = kShaderLevel3; + supportsRenderTextureFormat[kRTFormatDepth] = true; + supportsRenderTextureFormat[kRTFormatARGBHalf] = true; + hasComputeShader = false; + // GL specific + #if GFX_SUPPORTS_OPENGL + gl.hasGLSL = true; + #endif + // names + rendererString = "Emulated Shader Model 3.0"; + vendorString = "Emulated"; + #if UNITY_WIN + fixedVersionString = "Direct3D 9.0c [emulated]"; + #else + fixedVersionString = "OpenGL 2.0 [emulated]"; + #endif + break; + case kEmulSM2: + shaderCaps = kShaderLevel2; + supportsRenderTextureFormat[kRTFormatDepth] = true; + supportsRenderTextureFormat[kRTFormatARGBHalf] = false; + maxTexUnits = 4; + maxTexCoords = 8; + hasComputeShader = false; + hasInstancing = false; + // GL specific + #if GFX_SUPPORTS_OPENGL + gl.hasGLSL = true; + #endif + // names + rendererString = "Emulated Shader Model 2.0"; + vendorString = "Emulated"; + #if UNITY_WIN + fixedVersionString = "Direct3D 9.0c [emulated]"; + #else + fixedVersionString = "OpenGL 2.0 [emulated]"; + #endif + break; + case kEmulGLES20: + shaderCaps = kShaderLevel3; + //hasSRGBReadWrite = glesTargetAndroid; // while only supported on handful devices, lets allow people to use it in emu + hasSRGBReadWrite = false; + hasComputeShader = false; + hasInstancing = false; + supportsTextureFormat[kTexFormatAlphaLum16] = false; + supportsTextureFormat[kTexFormatDXT1] = glesTargetAndroid; + supportsTextureFormat[kTexFormatDXT3] = glesTargetAndroid; + supportsTextureFormat[kTexFormatDXT5] = glesTargetAndroid; + supportsTextureFormat[kTexFormatPVRTC_RGB2] = true; + supportsTextureFormat[kTexFormatPVRTC_RGBA2] = true; + supportsTextureFormat[kTexFormatPVRTC_RGB4] = true; + supportsTextureFormat[kTexFormatPVRTC_RGBA4] = true; + supportsTextureFormat[kTexFormatETC_RGB4] = true; + supportsTextureFormat[kTexFormatATC_RGB4] = glesTargetAndroid; + supportsTextureFormat[kTexFormatATC_RGBA8] = glesTargetAndroid; + supportsRenderTextureFormat[kRTFormatARGB32] = true; + supportsRenderTextureFormat[kRTFormatARGBHalf] = true; // supported on high-end mobiles ;-) + hasRenderToTexture = true; + hasTiledGPU = true; // assume tiled GPU when emulating ES2 + warnRenderTargetUnresolves = true; + + // don't touch 16bit rt formats, as they depend on underlying hw + + hasNativeShadowMap = false; + supportsRenderTextureFormat[kRTFormatDepth] = true; + supportsRenderTextureFormat[kRTFormatShadowMap] = false; // will use depth texture for shadows + + // There is no fixed function texture units in ES2 + maxTexUnits = maxTexImageUnits = maxTexCoords = 8; + maxMRTs = 1; + has3DTexture = false; + + // gles2.0 has hard shadows. + disableSoftShadows = true; + + hasRenderToCubemap = true; + npotRT = npot = kNPOTFull; + maxTextureSize = 4096; + maxCubeMapSize = 512; + // GL specific + #if GFX_SUPPORTS_OPENGL + gl.hasGLSL = true; + gl.hasTextureEnvCombine3ATI = false; + gl.hasTextureEnvCombine3NV = false; + #endif + // D3D9 specific + #if GFX_SUPPORTS_D3D9 + d3d.hasTextureFormatL16 = false; + d3d.hasTextureFormatA8L8 = false; + d3d.d3dcaps.MaxSimultaneousTextures = maxTexUnits; + d3d.d3dcaps.RasterCaps &= ~D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS; + #endif + rendererString = "Emulated GPU running OpenGL ES 2.0"; + vendorString = "Emulated"; + fixedVersionString = "OpenGL ES 2.0 [emulated]"; + break; + + case kEmulXBox360: + case kEmulPS3: + shaderCaps = kShaderLevel3; + hasFixedFunction = false; + + maxLights = 8; + maxMRTs = 1; //@TODO: possibly needs change for MRTs on console emulation + hasAnisoFilter = true; + maxAnisoLevel = 16; + hasMipLevelBias = true; + + hasMultiSample = (emul == kEmulPS3); + + hasComputeShader = false; + hasInstancing = false; + + hasBlendSquare = true; + hasSeparateAlphaBlend = true; + + hasS3TCCompression = true; + hasVertexTextures = true; + + hasAutoMipMapGeneration = false; + + maxTexImageUnits = 16; + maxTexCoords = 8; + + maxTexUnits = 8; + + maxTextureSize = 4096; + maxCubeMapSize = 512; + + supportsRenderTextureFormat[kRTFormatARGB32] = true; + supportsRenderTextureFormat[kRTFormatDepth] = true; + supportsRenderTextureFormat[kRTFormatARGBHalf] = true; + supportsRenderTextureFormat[kRTFormatShadowMap] = (emul == kEmulPS3); + + has3DTexture = true; + npotRT = npot = kNPOTFull; + + hasRenderToTexture = true; + hasRenderToCubemap = true; + hasTwoSidedStencil = true; + hasSRGBReadWrite = true; + hasNativeDepthTexture = true; + hasStencilInDepthTexture = true; + hasNativeShadowMap = (emul == kEmulPS3); + + rendererString = "Emulated Shader Model 3.0 (XBOX360/PS3)"; + vendorString = "Emulated"; + fixedVersionString = "XBOX360/PS3 (emulated)"; + break; + case kEmulDX11_9_1: + case kEmulDX11_9_3: + shaderCaps = emul == kEmulDX11_9_3 ? kShaderLevel3 : kShaderLevel2; + hasFixedFunction = emul == kEmulDX11_9_3; + + maxLights = 8; + maxMRTs = 1; + hasAnisoFilter = true; + maxAnisoLevel = emul == kEmulDX11_9_3 ? 16 : 2; + hasMipLevelBias = true; + + hasMultiSample = true; + + hasComputeShader = false; + hasInstancing = false; + + hasBlendSquare = true; + hasSeparateAlphaBlend = false; + + hasS3TCCompression = true; + hasVertexTextures = true; + + maxTexImageUnits = 16; + maxTexCoords = 8; + + maxTexUnits = 8; + + maxTextureSize = emul == kEmulDX11_9_3 ? 4096 : 2048; + maxCubeMapSize = emul == kEmulDX11_9_3 ? 4096 : 512; + + supportsRenderTextureFormat[kRTFormatARGB32] = true; + supportsRenderTextureFormat[kRTFormatDepth] = true; + supportsRenderTextureFormat[kRTFormatShadowMap] = true; + + has3DTexture = true; + npotRT = npot = kNPOTRestricted; + + hasTiledGPU = true; // assume tiled GPU when emulating DX11 9.x + warnRenderTargetUnresolves = true; + + hasRenderToTexture = true; + hasRenderToCubemap = false; + hasTwoSidedStencil = true; + hasSRGBReadWrite = true; + hasNativeDepthTexture = true; + hasStencilInDepthTexture = true; + hasNativeShadowMap = true; +#if GFX_SUPPORTS_D3D11 + d3d11.hasShadows10Level9 = true; +#endif + + rendererString = "Emulated DirectX 11 9.1 No Fixed Function Shaders"; + vendorString = "Emulated"; + fixedVersionString = "DirectX 11 9.1 (emulated)"; + break; + } +} + +void GraphicsCaps::InitializeOriginalEmulationCapsIfNeeded() +{ + if (!gDidInitializeOriginalCaps) + { + gDidInitializeOriginalCaps = true; + SET_ALLOC_OWNER(NULL); + gOriginalCaps = *this; + } +} + +void GraphicsCaps::ResetOriginalEmulationCaps() +{ + gDidInitializeOriginalCaps = false; +} + +bool GraphicsCaps::CheckEmulationSupported( Emulation emul ) +{ + InitializeOriginalEmulationCapsIfNeeded(); + + GraphicsCaps emulatedCaps = gOriginalCaps; + emulatedCaps.ApplyEmulationSetingsOnly(gOriginalCaps, emul); + + return CheckEmulationValid(gOriginalCaps, emulatedCaps); +} + +bool EmulationWants16bitDepth( GraphicsCaps::Emulation emul ) +{ + if( (emul == GraphicsCaps::kEmulGLES20) + && GetBuildTargetGroup(GetEditorUserBuildSettings().GetActiveBuildTarget()) == kPlatformAndroid + && !GetPlayerSettings().GetUse24BitDepthBuffer() + ) + { + return true; + } + + return false; +} + +GraphicsCaps::Emulation _CurrentEmulation = GraphicsCaps::kEmulNone; + +void GraphicsCaps::ApplyEmulationSettingsAffectingGUI() +{ + // well, not the best solution... + + if( EmulationWants16bitDepth(_CurrentEmulation) ) + GUIView::RecreateAllOnDepthBitsChange(32, 16); + else + GUIView::RecreateAllOnDepthBitsChange(16, 32); +} + +GraphicsCaps::Emulation GraphicsCaps::GetEmulation() const +{ + return _CurrentEmulation; +} + +void GraphicsCaps::SetEmulation( Emulation emul ) +{ + if( emul == _CurrentEmulation ) + return; + + if (!CheckEmulationSupported(emul)) + { + ErrorString("Attempting to switch to unsupported emulation"); + return; + } + + // must be released before the emulation is set, since the memory usage is estimated from the deviceCaps + RenderTexture::ReleaseAll(); + + _CurrentEmulation = emul; + + InitializeOriginalEmulationCapsIfNeeded(); + + *this = gOriginalCaps; + + GfxDevice& device = GetGfxDevice(); + device.InvalidateState(); + ShaderLab::SubProgram* nullShaders[kShaderTypeCount] = {0}; + GraphicsHelper::SetShaders (device, nullShaders, 0); + InvalidateGraphicsStateInEditorWindows(); + + ApplyEmulationSettingsAffectingGUI(); + ApplyEmulationSetingsOnly(gOriginalCaps, emul); + SharedCapsPostInitialize(); + + device.CommonReloadResources(GfxDevice::kReleaseRenderTextures | GfxDevice::kReloadShaders | GfxDevice::kReloadTextures); + + #if GFX_SUPPORTS_D3D9 + InitializeCombinerCapsD3D9(); + #endif + + // Give a chance for image effects (which mostly execute in edit mode + // as well) to recheck hardware support for them. + MonoBehaviour::RestartExecuteInEditModeScripts(); + + // update all lights + std::vector<Light*> lights; + Object::FindObjectsOfType (&lights); + for( std::vector<Light*>::iterator i = lights.begin(); i != lights.end(); ++i ) + { + Light& light = (**i); + light.Precalc(); + } +} + +bool GraphicsCaps::IsEmulatingGLES20() const +{ + return _CurrentEmulation == kEmulGLES20; +} + +#endif // UNITY_EDITOR + + +std::string GraphicsCaps::CheckGPUSupported() const +{ +#if !ENABLE_GFXDEVICE_REMOTE_PROCESS_CLIENT + GfxDeviceRenderer type = GetGfxDevice().GetRenderer(); + + // OpenGL +# if GFX_SUPPORTS_OPENGL + if (type == kGfxRendererOpenGL) + { + extern bool QueryExtensionGL (const char* ext); + + // OS X 10.5 on GMA 950 has GL1.2. When we drop 10.5 support we can move + // up to GL1.4. + if (gl.glVersion < 12) + { + return Format("OpenGL 1.2 is required. Your GPU (%s) only supports OpenGL %i.%i", rendererString.c_str(), gl.glVersion/10, gl.glVersion%10); + } + // Require GLSL + if (!gl.hasGLSL) + { + return Format("OpenGL GLSL support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + // Require ARB VP/FP + if (!QueryExtensionGL("GL_ARB_vertex_program") || !QueryExtensionGL("GL_ARB_fragment_program")) + { + return Format("OpenGL vertex/fragment program support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + + // Require VBO, multitexture, ... + if (gl.glVersion < 15 && !QueryExtensionGL("GL_ARB_vertex_buffer_object")) + { + return Format("OpenGL vertex buffer support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + if (gl.glVersion < 13 && !QueryExtensionGL ("GL_ARB_multitexture")) + { + return Format("OpenGL multitexture support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + if (gl.glVersion < 13 && !QueryExtensionGL ("GL_ARB_texture_cube_map")) + { + return Format("OpenGL cubemap support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + if (gl.glVersion < 13 && !QueryExtensionGL ("GL_ARB_texture_env_dot3")) + { + return Format("OpenGL dot3 combiner support is required. Your GPU (%s) does not support it", rendererString.c_str()); + } + } +# endif // if GFX_SUPPORTS_OPENGL + + + // D3D9 +# if GFX_SUPPORTS_D3D9 + if (type == kGfxRendererD3D9) + { + // Require SM2.0 at least. + // Note, vertex shaders can be reported as zero in case of software vertex processing. + // That is a valid & supported case (d3d runtime will run shaders on the CPU, but + // otherwise they will behave as supported). + const int kShaderVersion20 = (2 << 8) + 0; + const int d3dVS = LOWORD(d3d.d3dcaps.VertexShaderVersion); + const int d3dPS = LOWORD(d3d.d3dcaps.PixelShaderVersion); + if ((d3dVS != 0 && d3dVS < kShaderVersion20) || (d3dPS < kShaderVersion20)) + { + return Format("DirectX9 GPU (Shader Model 2.0) is required.\r\nYour GPU (%s)\r\nonly supports Shader Model %i.%i", rendererString.c_str(), d3dPS>>8, d3dPS&0xFF); + } + } +# endif // if GFX_SUPPORTS_D3D9 + + UNUSED(type); + +#endif + return ""; // all ok +} + + +void GraphicsCaps::SharedCapsPostInitialize() +{ + // do some sanity checks + Assert (!hasStencilInDepthTexture || (hasStencilInDepthTexture && hasNativeDepthTexture)); // stencil in depth texture implies native depth texture + + // requirements for deferred lighting + const int systemMemoryMB = systeminfo::GetPhysicalMemoryMB(); + + hasPrePassRenderLoop = +# if GFX_SUPPORTS_RENDERLOOP_PREPASS + (shaderCaps >= kShaderLevel3) && // needs SM3.0 + hasRenderToTexture && // needs render textures + supportsRenderTextureFormat[kRTFormatDepth] && // needs depth RT + hasRenderTargetStencil && // needs stencil in RT + hasTwoSidedStencil && // needs two sided stencil + (systemMemoryMB==0 || systemMemoryMB >= 450) // disable on old devices that have <512MB RAM (check for zero in case platform doesn't implement it at all...) +# else + false +# endif + ; +} + + +void GraphicsCaps::InitWithBuildVersion() +{ + #if GFX_SUPPORTS_OPENGL + // Before 4.2, we never used native shadow maps on GL, so keep that behavior. + if (GetGfxDevice().GetRenderer() == kGfxRendererOpenGL) + { + // First restore original flag (same player instance can be + // reused across different content versions) + hasNativeShadowMap = gl.originalHasNativeShadowMaps; + supportsRenderTextureFormat[kRTFormatShadowMap] = hasNativeShadowMap; + + if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1)) + { + hasNativeShadowMap = false; + supportsRenderTextureFormat[kRTFormatShadowMap] = false; + } + } + #endif +} + + +#if GFX_SUPPORTS_OPENGL || GFX_SUPPORTS_OPENGLES20 +bool FindGLExtension (const char* extensions, const char* ext) +{ + // Extension names should not have spaces, be NULL or empty + if (!ext || ext[0] == 0) + return false; + if (strchr(ext, ' ')) + return false; + + Assert (extensions != NULL); + if (!extensions) + return false; + + const char* start = extensions; + for (;;) + { + const char* where = strstr (start, ext); + if (!where) + break; + const char* terminator = where + strlen (ext); + if (where == start || *(where - 1) == ' ') + { + if (*terminator == ' ' || *terminator == '\0') + return true; + } + start = terminator; + } + return false; +} +#endif // GFX_SUPPORTS_OPENGL || GFX_SUPPORTS_OPENGLES20 + +#if GFX_SUPPORTS_OPENGLES20 +#if GFX_SUPPORTS_OPENGLES20 +# include "Runtime/GfxDevice/opengles20/IncludesGLES20.h" +#endif + +bool QueryExtension (const char* ext) +{ + static const char* extensions = NULL; + if (!extensions) + extensions = (const char*)glGetString(GL_EXTENSIONS); + if (!extensions) + return false; + return FindGLExtension (extensions, ext); +} + +#endif // GFX_SUPPORTS_OPENGLES20 + + + +// ------------------------------------------------------------------- + + + +#if ENABLE_UNIT_TESTS +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (GraphicsCapsTest) +{ + TEST (GraphicsCaps_DeviceIDs) + { + #if UNITY_EDITOR + if (gGraphicsCaps.GetEmulation() != GraphicsCaps::kEmulNone) + return; + #endif + + const int vendorID = gGraphicsCaps.vendorID; + if (vendorID == 0) + return; + + std::string vendor = ToLower(gGraphicsCaps.vendorString); + + if (vendorID == 0x10de) + { + CHECK (vendor.find("nvidia") != std::string::npos); + } + if (vendorID == 0x1002) + { + CHECK (vendor.find("ati") != std::string::npos || vendor.find("amd") != std::string::npos); + } + if (vendorID == 0x8086) + { + CHECK (vendor.find("intel") != std::string::npos); + } + } +} + +#endif // #if ENABLE_UNIT_TESTS diff --git a/Runtime/Shaders/GraphicsCaps.h b/Runtime/Shaders/GraphicsCaps.h new file mode 100644 index 0000000..0e1e37d --- /dev/null +++ b/Runtime/Shaders/GraphicsCaps.h @@ -0,0 +1,435 @@ +#pragma once + +#include <string> +#include "Runtime/GfxDevice/GfxDeviceTypes.h" + +enum ShaderCapsLevel +{ + kShaderLevel2 = 20, // shader model 2.0 & 2.x + kShaderLevel3 = 30, // shader model 3.0 + kShaderLevel4 = 40, // shader model 4.0 + kShaderLevel4_1 = 41, // shader model 4.1 + kShaderLevel5 = 50, // shader model 5.0 +}; + +enum NPOTCaps +{ + kNPOTNone = 0, // no NPOT texture capability + kNPOTRestricted, // NPOT available with restrictions (no mips, no wrap mode, no compression) + kNPOTFull, // NPOT available, no restrictions +}; + + +#if GFX_SUPPORTS_D3D9 +#include "PlatformDependent/Win/WinDriverUtils.h" +#include "Runtime/GfxDevice/d3d/D3D9Includes.h" +struct GraphicsCapsD3D9 +{ + D3DCAPS9 d3dcaps; + bool hasBaseTextureFormat[kTexFormatPCCount]; + bool hasTextureFormatA8; + bool hasTextureFormatL8; + bool hasTextureFormatA8L8; + bool hasTextureFormatL16; + bool hasATIDepthFormat16; // Has ATI DF16 render texture format? + bool hasNVDepthFormatINTZ; + bool hasNVDepthFormatRAWZ; + bool hasNULLFormat; + bool hasDepthResolveRESZ; + + // ---- hardware/driver workarounds + bool slowINTZSampling; // very slow at using depth testing and sampling INTZ at the same time; prefer RESZ copy into separate surface +}; +#endif + +#if GFX_SUPPORTS_D3D11 +enum DX11FeatureLevel { + kDX11Level9_1, + kDX11Level9_2, + kDX11Level9_3, + kDX11Level10_0, + kDX11Level10_1, + kDX11Level11_0, + kDX11LevelCount +}; + +struct GraphicsCapsD3D11 +{ + UInt32 msaa; // regular backbuffer, bit for each sample count we want + UInt32 msaaSRGB; // sRGB backbuffer, bit for each sample count we want + DX11FeatureLevel featureLevel; + bool hasShadows10Level9; + bool buggyPartialPrecision10Level9; // half precision broken on 10level9 shaders (crashes) +}; +#endif + +#if GFX_SUPPORTS_OPENGL +struct GraphicsCapsGL +{ + int glVersion; // e.g. 20 for 2.0, 43 for 4.3 + int vertexAttribCount; // How many generic vertex attributes are supported? + + int nativeVPInstructions; + int nativeFPInstructions; + int nativeFPTexInstructions; + int nativeFPALUInstructions; + int nativeFPTemporaries; + + int maxSamples; + + bool hasGLSL; // GLSL + + bool hasTextureEnvCombine3ATI; // GL_ATI_texture_env_combine3 - the preferred way + bool hasTextureEnvCombine3NV; // GL_NV_texture_env_combine4 - fallback to emulate with TNT combiners + + #if UNITY_WIN + bool hasWglARBPixelFormat; + bool hasWglSwapControl; + #endif + + bool hasArbMapBufferRange; + bool hasArbSync; + + #if UNITY_OSX + bool hasAppleFence; + bool hasAppleFlushBufferRange; + #endif + + bool hasFrameBufferBlit; + + // ---- hardware/driver workarounds + bool buggyArbPrecisionHint; // ARB precision hint in FPs is broken, postprocess programs to remove it + bool cacheFPParamsWithEnvs; // For fragment programs, use Env parameters instead of Local ones, and cache their values + bool forceColorBufferWithDepthFBO; // For depth FBO, always use color buffer, with no color buffer it will be wrong + bool force24DepthForFBO; // 16 bit depth does not work for FBO depth textures + bool buggyPackedDepthStencil; + bool mustWriteToDepthBufferBeforeClear; // depth buffer will contain garbage if it isn't written to at least once per frame. + bool originalHasNativeShadowMaps; +}; +#endif + + + +#if GFX_SUPPORTS_OPENGLES20 +struct GraphicsCapsGLES20 +{ + int maxAttributes; + int maxVaryings; + int maxSamples; + bool hasGLSL; + bool hasVBOOrphaning; + bool slowAlphaTest; + bool slowDynamicVBO; // will force dynamic draw from mem (except static batch indices) + bool forceStaticBatchFromMem; // will force static batch indices to be used from mem + + // features + + bool hasBinaryShaders; + bool has24DepthForFBO; + bool hasMapbuffer; + bool hasMapbufferRange; + bool hasDebugMarkers; + bool hasDiscardFramebuffer; + bool hasHalfLinearFilter; + + bool hasAppleMSAA; + bool hasImgMSAA; + + // bugs workaround + + bool buggyColorMaskBlendMSAA; // on some ios msaa+color mask+blend results in crazy rendering artefacts + bool buggyDisableVAttrKeepsActive; // on some mali drivers vattrs are kept active even after disabling them + bool forceHighpFSPrec; // on some nvidia drivers there is a bug: they fail to report samplers if default prec is not high + bool buggyVFetchPatching; // some adreno drivers fails to patch vfetch instructions (if vertex layout changed) + bool buggyVprogTextures; // some mali drivers have crashes in shader compiler if vprog texture is used + bool needToRenderWatermarkDueToDrawFromMemoryBuggy; // on some pvr it may crash when rendering from mem (it seems one shader used is culprit) + bool needFlushAfterTextureUpload; + + // vendor specific + + bool hasNLZ; // nvidia non-linear z + bool hasNVMRT; // nvidia mrt support + bool hasAlphaTestQCOM; // adreno special gles11 alpha test +}; +#endif + +#if GFX_SUPPORTS_OPENGLES30 +struct GraphicsCapsGLES30 +{ + GraphicsCapsGLES30 (void) + : maxAttributes (16) + , maxVaryings (16) + , maxSamples (4) + , useProgramBinary (true) + , useMapBuffer (true) + , useTFSkinning (true) + , hasDebugMarkers (false) + , hasAlphaTestQCOM (false) + { + } + + // Limits. + int maxAttributes; + int maxVaryings; + int maxSamples; // Max sample count for renderbuffers. + + // Feature configuration. + bool useProgramBinary; // ProgramBinary support is in core, but in case of we want to ignore it on some platforms. + bool useMapBuffer; // One some platforms mapping buffers is very slow. + bool useTFSkinning; // Use transform feedback for skinning. + + // Extension bits. + bool hasDebugMarkers; + bool hasAlphaTestQCOM; +}; +#endif + +#if GFX_SUPPORTS_OPENGL || GFX_SUPPORTS_OPENGLES20 +bool FindGLExtension (const char* extensions, const char* ext); +#endif + +#if GFX_SUPPORTS_OPENGLES20 +bool QueryExtension (const char* ext); +#endif + + +struct GraphicsCaps +{ + GraphicsCaps() + { + ::memset(this, 0x00, sizeof(GraphicsCaps)); + + for (int i = 0 ; i < kTexFormatPCCount; ++i) + supportsTextureFormat[i] = true; + + // Default RT format is always enabled until we don't have render-to-texture at all + // It is dummy value used to select most appropriate rt format + supportsRenderTextureFormat[kRTFormatDefault] = true; + supportsRenderTextureFormat[kRTFormatDefaultHDR] = true; + + new (&rendererString) StaticString(); + new (&vendorString) StaticString(); + new (&driverVersionString) StaticString(); + new (&fixedVersionString) StaticString(); + new (&driverLibraryString) StaticString(); + + shaderCaps = kShaderLevel2; + videoMemoryMB = 16.0f; + + maxLights = 8; + maxAnisoLevel = 1; + maxTexImageUnits = 8; + maxTexCoords = 1; + maxTexUnits = 1; + + maxTextureSize = 256; + maxCubeMapSize = 64; + maxRenderTextureSize = 128; + maxMRTs = 1; + + hasFixedFunction = true; + npotRT = npot = kNPOTNone; + hasNonFullscreenClear = true; + hasShadowCollectorPass = true; + hasHighPrecisionTextureCombiners = true; + + hasGrabPass = true; + } + + void SharedCapsPostInitialize(); + + // ---------- caps common for all devices + StaticString rendererString; // graphics card name + StaticString vendorString; // graphics card vendor name + StaticString driverVersionString; // (GL) version as reported by the driver + StaticString fixedVersionString; // (GL) correct GL version appended in front by us + StaticString driverLibraryString; // Name of driver's DLL and version + int vendorID; + int rendererID; + + ShaderCapsLevel shaderCaps; // Generic "shader capability level" + + float videoMemoryMB; // Approx. amount of video memory in MB. Used to limit texture, render target and so on sizes to sane values. + + int maxVSyncInterval; // Max frames that device can wait for vertical blank + int maxLights; // vertex light count + int maxAnisoLevel; + int maxTexImageUnits; // Part of arb-fragment program (otherwise they match textureUnitCount) + int maxTexCoords; + int maxTexUnits; + + int maxTextureSize; + int maxCubeMapSize; + int maxRenderTextureSize; // usually maxTextureSize, except on some crappy cards + + int maxMRTs; + + bool hasAnisoFilter; // has anisotropic filtering? + bool hasMipLevelBias; // can apply bias for mips? + bool hasMipMaxLevel; // can specify max mip level + + bool hasFixedFunction; // Supports T&L and texture combine stages + + bool hasMultiSample; + bool hasMultiSampleAutoResolve; // uses texture instead, and under-the-hood rb will be auto-resolved to it + + bool hasBlendSquare; + bool hasSeparateAlphaBlend; + bool hasBlendSub; // both sub and revsub + bool hasBlendMinMax; // max,min + bool hasBlendLogicOps; // kBlendOpLogical* + + bool hasS3TCCompression; + bool hasVertexTextures; // Can read textures in a vertex shader? + + bool hasTimerQuery; + + bool hasAutoMipMapGeneration; // can auto-generate mipmaps for textures? + + bool supportsTextureFormat[kTexFormatTotalCount]; + bool supportsRenderTextureFormat[kRTFormatCount]; + + bool has3DTexture; + + NPOTCaps npot; + NPOTCaps npotRT; + + bool hasSRGBReadWrite; + bool hasComputeShader; + bool hasInstancing; + bool hasNonFullscreenClear; // Can do clears on non-full screen? (e.g. D3D11 can not) + + bool hasRenderToTexture; // We have render-to-texture functionality + bool hasRenderToCubemap; // We have render-to-cubemap functionality + bool hasRenderTo3D; // We have render-to-volume functionality + bool hasStencil; // Stencil support good enough to be used by the users via the shaderlab state. + bool hasRenderTargetStencil; // Has sane way of having stencil buffer on render targets + bool hasNativeDepthTexture; // Depth textures come from actual depth buffer + bool hasStencilInDepthTexture; // Has native depth texture AND stencil buffer of it can be used at the same time + bool hasNativeShadowMap; // Depth textures have native shadow map comparison sampling + bool hasShadowCollectorPass; // mobiles don't usage collector pass, go direct SMs! + bool hasTiledGPU; // Uses tiled rendering (clear/discard preferred!) + + bool hasTwoSidedStencil; + bool hasHighPrecisionTextureCombiners; + + bool has16BitFloatVertex; // Supports at least 2D and 4D vertex channels as 16 bit floats + bool needsToSwizzleVertexColors; // Should vertex colors be passed as B,G,R,A bytes instead of R,G,B,A + + bool disableSubTextureUpload; // Can we do UploadTextureSubData2D? + bool warnRenderTargetUnresolves; // Warn when doing RT un-resolves + + bool hasGrabPass; // Can read from active render target back into a render texture? + + // cross platform caps initialized in SharedCapsPostInitialize + bool hasPrePassRenderLoop; + + bool SupportsRGBM() const { return maxTexUnits >= 3 && hasHighPrecisionTextureCombiners; } + + std::string CheckGPUSupported() const; + + // ---- hardware/driver workarounds for all renderers + + bool disableSoftShadows; // Soft shadows should work, but the driver is buggy on those shaders + bool buggyCameraRenderToCubemap;// Render to cubemap sort-of-does-not-work (but works for point light shadows). + bool buggyFullscreenFSAA; // FSAA in fullscreen is buggy. + bool buggyTextureBothColorAndDepth; // Can't have render target where both color & depth will be sampled as textures + bool buggyDynamicVBOWithTangents; // DynamicVBO with tangents is buggy + bool buggyMipmappedCubemaps; // Cubemaps with mipmaps are buggy; keep only 1st mip level + bool buggyMipmapped3DTextures; // 3D textures with mipmaps are buggy; keep only 1st mip level + bool buggyShadowMapBilinearSampling; // Never use hardware bilinear PCF on shadow maps due to bugs? + bool buggySpotNativeShadowMap; // Never use hardware shadow maps on spot lights + bool buggyTimerQuery; // Has timer queries, but they aren't very good! + + // ---- caps specific for renderers + +#if GFX_SUPPORTS_OPENGL + GraphicsCapsGL gl; +#endif +#if GFX_SUPPORTS_OPENGLES20 + GraphicsCapsGLES20 gles20; +#endif +#if GFX_SUPPORTS_D3D9 + GraphicsCapsD3D9 d3d; +#endif +#if GFX_SUPPORTS_D3D11 + GraphicsCapsD3D11 d3d11; +#endif +#if GFX_SUPPORTS_OPENGLES30 + GraphicsCapsGLES30 gles30; +#endif + + #if GFX_SUPPORTS_OPENGL + void InitGL(); + #endif + #if GFX_SUPPORTS_D3D9 + void InitD3D9(); + #endif + #if GFX_SUPPORTS_D3D11 + void InitD3D11(); + #endif + #if GFX_SUPPORTS_NULL + void InitNull(); + #endif + #if GFX_SUPPORTS_MOLEHILL + void InitMolehill(); + #endif + #if GFX_SUPPORTS_XENON + void InitXenon(); + #endif + #if GFX_SUPPORTS_GCM + void InitGcm(); + UInt32 videoMemoryStart; + #endif + #if GFX_SUPPORTS_HOLLYWOOD + void InitHollywood(); + #endif + #if GFX_SUPPORTS_OPENGLES20 + void InitGLES20(); + #endif + #if GFX_SUPPORTS_OPENGLES30 + void InitGLES30(); + #endif + + void InitWithBuildVersion(); + + + // Implemented in GraphicsCaps.cpp + #if UNITY_EDITOR + enum Emulation { + kEmulNone, // whatever card naturally has + kEmulSM3, // Shader Model 3.0 + kEmulSM2, // Shader Model 2.0, but with 4 texture stages + kEmulGLES20, // OpenGL ES 2.0 + kEmulXBox360, + kEmulPS3, + kEmulDX11_9_1, // DirectX 11 Feature Level 9.1, No Fixed Function shaders (Shader Model 2) + kEmulDX11_9_3, // DirectX 11 Feature Level 9.1, Fixed Function shaders (Shader Model 3) + kEmulCount + }; + Emulation GetEmulation() const; + void SetEmulation( Emulation emul ); + bool IsEmulatingGLES20() const; + bool CheckEmulationSupported( Emulation emul ); + void InitializeOriginalEmulationCapsIfNeeded(); + void ResetOriginalEmulationCaps(); + + static void ApplyEmulationSettingsAffectingGUI(); + + private: + void ApplyEmulationSetingsOnly( const GraphicsCaps& origCaps, const Emulation emul ); + #endif + + +private: + + #if GFX_SUPPORTS_OPENGL + void AdjustBuggyVersionGL( int& version, int& major, int& minor ) const; + void DetectDriverBugsGL( int version ); + #endif + #if GFX_SUPPORTS_D3D9 + void DetectDriverBugsD3D9( UInt32 vendorCode, const windriverutils::VersionInfo& version ); + #endif +}; + +extern GraphicsCaps gGraphicsCaps; diff --git a/Runtime/Shaders/Material.cpp b/Runtime/Shaders/Material.cpp new file mode 100644 index 0000000..3e6aaf5 --- /dev/null +++ b/Runtime/Shaders/Material.cpp @@ -0,0 +1,1096 @@ +#include "UnityPrefix.h" +#include "Material.h" +#include "Shader.h" +#include "ShaderNameRegistry.h" +#include "External/shaderlab/Library/properties.h" +#include "Runtime/Misc/BuildSettings.h" +#include "Runtime/Misc/ResourceManager.h" +#include "External/shaderlab/Library/intshader.h" +#include "External/shaderlab/Library/texenv.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Graphics/ProceduralMaterial.h" +#include "Runtime/GfxDevice/GfxDisplayList.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Math/ColorSpaceConversion.h" +#include "Runtime/Profiler/Profiler.h" + +#if UNITY_EDITOR +#include "External/shaderlab/Library/SLParserData.h" +#include "Runtime/Mono/MonoManager.h" +#include "Runtime/Scripting/Scripting.h" +#include "Runtime/Scripting/Backend/ScriptingInvocation.h" + +extern ShaderLab::ShaderState* g_EditorPixelShaderOverride; +#endif + + +PROFILER_INFORMATION(gSetShadowCasterPass, "Shader.SetShadowCasterPass", kProfilerRender); +PROFILER_INFORMATION(gSetShadowCollectorPass, "Shader.SetShadowCollectorPass", kProfilerRender); + + +using namespace std; + +namespace Unity +{ + +template<class TransferFunc> +void Material::Transfer (TransferFunc& transfer) +{ + transfer.SetVersion (3); + Super::Transfer (transfer); + transfer.Transfer (m_Shader, "m_Shader"); + transfer.Transfer (m_ShaderKeywords, "m_ShaderKeywords"); + transfer.Transfer (m_CustomRenderQueue, "m_CustomRenderQueue"); + +#if UNITY_EDITOR + if (transfer.IsBuildingTargetPlatform(kBuild_Android) && m_Shader && m_Shader->HasClip()) + { + DebugStringToFile ( "Shader is using clip instruction (usually caused by alpha test). It might cause problems on some Qualcomm/Adreno drivers.", + 0, __FILE__, __LINE__, kAssetImportWarning, GetInstanceID() + ); + + } +#endif + + // Cull unused properties when making build! + #if UNITY_EDITOR + if ((transfer.GetFlags () & kBuildPlayerOnlySerializeBuildProperties)) + { + Shader *shader = m_Shader; + if (shader) + { + UnityPropertySheet tempProps = m_SavedProperties; + tempProps.CullUnusedProperties (shader->GetParsedForm()); + transfer.Transfer (tempProps, "m_SavedProperties"); + } + else + { + UnityPropertySheet tempProps; + transfer.Transfer (tempProps, "m_SavedProperties"); + } + } + else + #endif + { + TRANSFER (m_SavedProperties); + } +} + + +void Material::Reset() +{ + Super::Reset(); + + m_CustomRenderQueue = -1; + + ClearProperties(); + m_SavedProperties = UnityPropertySheet(); + + Shader *shader = m_Shader; + if( !shader ) + shader = Shader::GetDefault(); + + BuildShaderKeywordSet (); // build keywords before properties, so that pass hashing gets correct keywords + BuildProperties (); +#if UNITY_EDITOR + ResetDefaultTextures (true); + if (GetCachedScriptingObject()) + ApplyMaterialPropertyDrawers(); +#endif +} + + +int Material::GetActualRenderQueue() const +{ + // Use custom render queue if set (case 548478) + if (m_CustomRenderQueue >= 0) + return m_CustomRenderQueue; + + const Shader* shader = GetShader(); + Assert (shader); + return shader->GetShaderLabShader()->GetRenderQueue(); +} + + +void Material::BuildProperties () +{ + SET_ALLOC_OWNER(NULL); + SAFE_RELEASE_LABEL(m_Properties,kMemShader); + Shader *shader = m_Shader; + if( !shader ) + shader = Shader::GetDefault(); + + // This happens on Flash when loading some materials - at AwakeFromLoad + // time, the shader is not parsed yet (?). Just return in that case, the + // properties will be build later on demand. Worst that can happen is that + // for one frame shadow casters might get batched slightly wrong. + if (!shader->GetShaderLabShader()) + return; + + // Build the shaderlab property sheet. + m_Properties = shader->MakeProperties(); + m_Properties->SetOwnerMaterial(this); + + // Make sure our serialized properties (m_SavedProperties) have + // all the properties from the shader's properties block. + // (we might not be up-to-date if the shader has changed and added more + // properties). + m_SavedProperties.AddNewShaderlabProps (*shader->GetShaderLabShader()->GetDefaultProperties()); + + /// Get the properties from the material. + m_SavedProperties.AssignDefinedPropertiesTo (*m_Properties); + + if( m_Shader ) + m_Shader->AddMaterialUser( m_ShaderUserNode ); + + UpdateHashes(); +} + + +void Material::UpdateHashesOnPropertyChange (ShaderLab::FastPropertyName name) +{ + Shader* shader = m_Shader; + if (!shader || !m_Properties) + return; + ShaderLab::Pass* passCaster = shader->GetShadowCasterPass(); + if (passCaster) + { + if (passCaster->IsPropertyAffectingPass(name)) + m_ShadowCasterHash = passCaster->ComputePassValuesHash (m_ShaderKeywordSet, m_Properties); + } + ShaderLab::Pass* passCollector = shader->GetShadowCollectorPass(); + if (passCollector) + { + if (passCollector->IsPropertyAffectingPass(name)) + m_ShadowCollectorHash = passCollector->ComputePassValuesHash (m_ShaderKeywordSet, m_Properties); + } + + const dynamic_array<int>& propsAffectingBlocks = shader->GetShaderLabShader()->GetPropsAffectingStateBlocks(); + if (std::find(propsAffectingBlocks.begin(), propsAffectingBlocks.end(), name.index) != propsAffectingBlocks.end()) + { + m_StateKeyHash = ShaderLab::ComputeStateBlockValuesHash (propsAffectingBlocks, m_Properties); + shader->GetShaderLabShader()->CreateStateBlocksForKey (m_StateKeyHash, m_Properties); + } +} + +void Material::UpdateHashes () +{ + m_ShadowCasterHash = 0; + m_ShadowCollectorHash = 0; + m_StateKeyHash = 0; + Shader* shader = m_Shader; + if (!shader || !m_Properties) + return; + ShaderLab::Pass* passCaster = shader->GetShadowCasterPass(); + if (passCaster) + m_ShadowCasterHash = passCaster->ComputePassValuesHash (m_ShaderKeywordSet, m_Properties); + ShaderLab::Pass* passCollector = shader->GetShadowCollectorPass(); + if (passCollector) + m_ShadowCollectorHash = passCollector->ComputePassValuesHash (m_ShaderKeywordSet, m_Properties); + + m_StateKeyHash = ShaderLab::ComputeStateBlockValuesHash (shader->GetShaderLabShader()->GetPropsAffectingStateBlocks(), m_Properties); + shader->GetShaderLabShader()->CreateStateBlocksForKey (m_StateKeyHash, m_Properties); +} + + +void Material::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + + BuildShaderKeywordSet (); // build keywords before properties, so that pass hashing gets correct keywords + + ClearProperties (); + + // Enforce that everything is preloaded in the player, reduces hiccups at runtime + // Don't do that in the editor because it can easily run out of memory and increase build times. + #if !UNITY_EDITOR + BuildProperties(); + #endif +} + + +Material::Material(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_ShaderUserNode(this) +{ + m_Properties = NULL; + m_PropertiesDirty = false; + m_Shader = 0; + m_CustomRenderQueue = -1; + m_ShadowCasterHash = 0; + m_ShadowCollectorHash = 0; + m_StateKeyHash = 0; +} + +Material::~Material() +{ + SAFE_RELEASE_LABEL(m_Properties, kMemShader); + InvalidateDisplayLists(); +} + +void Material::SetShader (Shader *s) +{ + m_ShaderUserNode.RemoveFromList(); // remove ourselves from old shader's users + + m_Shader = s; + if( !s ) + { + SetDirty (); + return; + } + + BuildProperties (); + + SetDirty (); + InvalidateDisplayLists(); +} + +Material *Material::CreateMaterial (const char *shaderStr, int hideFlags, bool scriptingObjectIsBeingCreated) +{ + // Create temporary shader + Shader *shader = NEW_OBJECT (Shader); + shader->Reset(); + + shader->SetHideFlags(hideFlags); + shader->SetScript (shaderStr); + #if UNITY_EDITOR + shader->GetErrors().LogErrors (shader->GetName(), shader->GetNamedObjectName(), shader, shader->GetInstanceID()); + #endif + + shader->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad); + + // TODO: in editor parse shaderlab errors to console + //shader->ParseShaderErrorsToConsole(); + return CreateMaterial(*shader, hideFlags, scriptingObjectIsBeingCreated); +} + +Material *Material::CreateMaterial (Shader& shader, int hideFlags, bool scriptingObjectIsBeingCreated) +{ + // Create temporary material + Material *mat = CreateObjectFromCode<Material>(); + + mat->SetHideFlags(hideFlags); + mat->SetName(shader.GetName ()); + mat->m_Shader = &shader; + + mat->BuildProperties (); + +#if UNITY_EDITOR + mat->ResetDefaultTextures (true); + if (!scriptingObjectIsBeingCreated) + mat->ApplyMaterialPropertyDrawers(); +#endif + + return mat; +} + +Material *Material::CreateMaterial (const Material& material, int hideFlags, bool scriptingObjectIsBeingCreated) +{ + // Create temporary material + Material *mat = CreateObjectFromCode<Material>(); + + mat->SetHideFlags(hideFlags); + mat->SetName(material.GetName ()); + + mat->m_Shader = material.m_Shader; + mat->m_SavedProperties.AddNewSerializedProps (material.m_SavedProperties); + mat->BuildProperties (); + + return mat; +} + +Material *Material::GetDefault () +{ + static Material* s_DefaultMaterial = NULL; + if (!s_DefaultMaterial) { + s_DefaultMaterial = CreateObjectFromCode<Material>(); + s_DefaultMaterial->SetHideFlags (kHideAndDontSave); + } + return s_DefaultMaterial; +} + +Material *Material::GetDefaultDiffuseMaterial () +{ + #if WEBPLUG + if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion_OldWebResourcesAdded)) + return GetBuiltinOldWebResource<Material> ("Default-Diffuse.mat"); + #endif + + #if UNITY_EDITOR + // In the editor, important to return an asset here: e.g. if we create a cube, + // then we want to turn that into a prefab with a proper material. + return GetBuiltinExtraResource<Material> ("Default-Diffuse.mat"); + #endif + + static PPtr<Material> s_DefaultDiffuseMaterial; + if (s_DefaultDiffuseMaterial.IsNull()) + { + Shader* shader = GetScriptMapper().FindShader("Diffuse"); + if (!shader) + shader = Shader::GetDefault(); + s_DefaultDiffuseMaterial = CreateMaterial(*shader, kHideAndDontSave); + } + return s_DefaultDiffuseMaterial; +} + +struct ApplyKeywords +{ + ShaderKeywordSet prev; + ApplyKeywords (ShaderKeywordSet mask) { + prev = g_ShaderKeywords; + g_ShaderKeywords.SetMask (prev.GetMask () | mask.GetMask ()); + } + ~ApplyKeywords () { + g_ShaderKeywords = prev; + } +}; + +const ChannelAssigns* Material::SetPass (int passNo, int subshaderIndex, bool allowRecording) +{ + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + + Shader *shader = m_Shader; + if (!shader) + shader = Shader::GetDefault(); + EnsurePropertiesExist(); + + if (m_PropertiesDirty) + InvalidateDisplayLists(); + +#if GFX_SUPPORTS_DISPLAY_LISTS + + // editor shader override won't work with recorded display lists + #if UNITY_EDITOR + if (g_EditorPixelShaderOverride) + allowRecording = false; + #endif + + CachedShaderPass* cachedPass = NULL; + if (allowRecording && !m_PropertiesDirty) + { + if (subshaderIndex >= m_CachedSubShaders.size()) + m_CachedSubShaders.resize(subshaderIndex + 1); + CachedSubShader& subshader = m_CachedSubShaders[subshaderIndex]; + if (passNo >= subshader.passes.size()) + subshader.passes.resize_initialized(passNo + 1); + cachedPass = &subshader.passes[passNo]; + } + + GfxDevice& device = GetGfxDevice(); + + UInt64 shaderKeywords = g_ShaderKeywords.GetMask(); + using ShaderLab::g_GlobalFogMode; + bool recording = false; + if (cachedPass) + { + if (cachedPass->displayList && + cachedPass->shaderKeywords == shaderKeywords && + cachedPass->globalFogMode == g_GlobalFogMode) + { + cachedPass->displayList->Call(); + return cachedPass->channelAssigns; + } + SAFE_RELEASE(cachedPass->displayList); + if (shader->CanPassBeRecorded(subshaderIndex, passNo)) + recording = device.BeginRecording(); + } + +#endif + + const ChannelAssigns* channels; + channels = shader->SetPass(subshaderIndex, passNo, m_StateKeyHash, &GetProperties()); + +#if GFX_SUPPORTS_DISPLAY_LISTS + if (recording && device.EndRecording(&cachedPass->displayList)) + { + cachedPass->channelAssigns = channels; + cachedPass->shaderKeywords = shaderKeywords; + cachedPass->globalFogMode = g_GlobalFogMode; + } +#endif + return channels; +} + +const ChannelAssigns* Material::SetPassWithShader( int passNo, Shader* shader, int subshaderIndex ) +{ + if (shader == m_Shader) + return SetPass(passNo, subshaderIndex); + + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + Assert(shader != NULL); + return shader->SetPass( subshaderIndex, passNo, m_StateKeyHash, &GetProperties() ); +} + +const ChannelAssigns* Material::SetShadowCasterPassWithShader(Shader* shader, int subshaderIndex) +{ + if (shader == m_Shader) + return SetShadowCasterPass(subshaderIndex); + + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + PROFILER_AUTO(gSetShadowCasterPass, this) + ShaderLab::Pass* pass = shader->GetShadowCasterPassToUse(subshaderIndex); + return pass->ApplyPass(m_StateKeyHash, &GetProperties()); +} + +const ChannelAssigns* Material::SetShadowCasterPass(int subshaderIndex) +{ + PROFILER_AUTO(gSetShadowCasterPass, this) + + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + Shader *shader = GetShader(); + ShaderLab::Pass* pass = shader->GetShadowCasterPassToUse(subshaderIndex); + EnsurePropertiesExist(); + +#if GFX_SUPPORTS_DISPLAY_LISTS + if (m_PropertiesDirty) + InvalidateDisplayLists(); + + UInt64 shaderKeywords = g_ShaderKeywords.GetMask(); + bool recording = false; + bool allowRecording = true; + + // editor shader override won't work with recorded display lists + #if UNITY_EDITOR + if (g_EditorPixelShaderOverride) + allowRecording = false; + #endif + + GfxDevice& device = GetGfxDevice(); + CachedShaderPass* cachedPass = NULL; + if (!m_PropertiesDirty && allowRecording) + { + if (subshaderIndex >= m_CachedSubShaders.size()) + m_CachedSubShaders.resize(subshaderIndex + 1); + + CachedSubShader& subshader = m_CachedSubShaders[subshaderIndex]; + cachedPass = &subshader.shadowCasterPass; + Assert (cachedPass != NULL); + + if (cachedPass->displayList && + cachedPass->shaderKeywords == shaderKeywords) + { + cachedPass->displayList->Call(); + return cachedPass->channelAssigns; + } + SAFE_RELEASE(cachedPass->displayList); + if (pass->CanPassBeRecorded()) + recording = device.BeginRecording(); + } +#endif + + const ChannelAssigns* channels; + channels = pass->ApplyPass(m_StateKeyHash, &GetProperties()); + +#if GFX_SUPPORTS_DISPLAY_LISTS + if (recording && device.EndRecording(&cachedPass->displayList)) + { + cachedPass->channelAssigns = channels; + cachedPass->shaderKeywords = shaderKeywords; + } +#endif + + return channels; +} + +const ChannelAssigns* Material::SetShadowCollectorPassWithShader(Shader* shader, int subshaderIndex) +{ + if (shader == m_Shader) + return SetShadowCollectorPass(subshaderIndex); + + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + PROFILER_AUTO(gSetShadowCollectorPass, this) + ShaderLab::Pass* pass = shader->GetShadowCollectorPassToUse(subshaderIndex); + return pass->ApplyPass(m_StateKeyHash, &GetProperties()); +} + +const ChannelAssigns* Material::SetShadowCollectorPass(int subshaderIndex) +{ + PROFILER_AUTO(gSetShadowCollectorPass, this) + ApplyKeywords applyKeywords (m_ShaderKeywordSet); + Shader *shader = GetShader(); + ShaderLab::Pass* pass = shader->GetShadowCollectorPassToUse(subshaderIndex); + EnsurePropertiesExist(); + +#if GFX_SUPPORTS_DISPLAY_LISTS + if (m_PropertiesDirty) + InvalidateDisplayLists(); + + UInt64 shaderKeywords = g_ShaderKeywords.GetMask(); + bool recording = false; + bool allowRecording = true; + + // editor shader override won't work with recorded display lists +#if UNITY_EDITOR + if (g_EditorPixelShaderOverride) + allowRecording = false; +#endif + + GfxDevice& device = GetGfxDevice(); + CachedShaderPass* cachedPass = NULL; + if (!m_PropertiesDirty && allowRecording) + { + if (subshaderIndex >= m_CachedSubShaders.size()) + m_CachedSubShaders.resize(subshaderIndex + 1); + + CachedSubShader& subshader = m_CachedSubShaders[subshaderIndex]; + cachedPass = &subshader.shadowCollectorPass; + Assert (cachedPass != NULL); + + if (cachedPass->displayList && + cachedPass->shaderKeywords == shaderKeywords) + { + cachedPass->displayList->Call(); + return cachedPass->channelAssigns; + } + SAFE_RELEASE(cachedPass->displayList); + if (pass->CanPassBeRecorded()) + recording = device.BeginRecording(); + } +#endif + + const ChannelAssigns* channels; + channels = pass->ApplyPass(m_StateKeyHash, &GetProperties()); + +#if GFX_SUPPORTS_DISPLAY_LISTS + if (recording && device.EndRecording(&cachedPass->displayList)) + { + cachedPass->channelAssigns = channels; + cachedPass->shaderKeywords = shaderKeywords; + } +#endif + return channels; +} + +const Shader *Material::GetShader() const +{ + const Shader *shader = m_Shader; + if (shader) + return shader; + else + return Shader::GetDefault(); +} + +PPtr<Shader> Material::GetShaderPPtr() const +{ + return m_Shader; +} + + +Shader *Material::GetShader() +{ + Shader *shader = m_Shader; + if (shader) + return shader; + else + return Shader::GetDefault(); +} +std::string Material::GetTag( const string& tag, bool currentSubShaderOnly, const string& defaultValue ) const +{ + Shader* shader = m_Shader; + if (!shader) + return defaultValue; + int tagValueID = shader->GetShaderLabShader()->GetTag (ShaderLab::GetShaderTagID(tag), currentSubShaderOnly); + if (tagValueID < 0) + return defaultValue; + return ShaderLab::GetShaderTagName(tagValueID); +} + +int Material::GetPassCount() +{ + Shader* shader = m_Shader; + if( !shader ) + shader = Shader::GetDefault(); + return shader->GetShaderLabShader()->GetActiveSubShader().GetValidPassCount(); +} + +Material& Material::GetInstantiatedMaterial (Material* material, Object& renderer, bool allowInEditMode) +{ + if (material == NULL) + material = GetDefaultDiffuseMaterial(); + + if (material->m_Owner == PPtr<Object> (&renderer)) + return *material; + else + { + if (!allowInEditMode && !IsWorldPlaying()) + ErrorStringObject("Instantiating material due to calling renderer.material during edit mode. This will leak materials into the scene. You most likely want to use renderer.sharedMaterial instead.", &renderer); + + Material* instance; + if (material->GetClassID()==ProceduralMaterial::GetClassIDStatic()) + { + instance = static_cast<ProceduralMaterial*>(material)->Clone(); + } + else + { + instance = CreateObjectFromCode<Material>(); + } + + instance->SetNameCpp (Append (material->GetName (), " (Instance)")); + instance->m_Shader = material->m_Shader; + instance->m_Owner = &renderer; + if (material->m_Properties) + { + SET_ALLOC_OWNER(instance); + SAFE_RELEASE_LABEL(instance->m_Properties, kMemShader); + instance->m_Properties = UNITY_NEW(ShaderLab::PropertySheet (*material->m_Properties), kMemShader); + instance->m_Properties->SetOwnerMaterial(instance); + } + instance->m_CustomRenderQueue = material->m_CustomRenderQueue; + instance->m_SavedProperties = material->m_SavedProperties; + if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1)) + { + instance->m_ShaderKeywords = material->m_ShaderKeywords; + instance->m_ShaderKeywordSet = material->m_ShaderKeywordSet; + } + instance->m_ShadowCollectorHash = material->m_ShadowCollectorHash; + instance->m_ShadowCasterHash = material->m_ShadowCasterHash; + instance->m_StateKeyHash = material->m_StateKeyHash; + if( instance->m_Shader ) + instance->m_Shader->AddMaterialUser( instance->m_ShaderUserNode ); + return *instance; + } +} + + + +#if UNITY_EDITOR +inline void EmitWarningAboutSettingBuiltinParam(const char* name) +{ + static const std::string kWarningPrefix = std::string("Trying to set builtin parameter \""); + static const std::string kWarningSuffix = std::string("\". Will be ignored."); + + WarningString( kWarningPrefix + name + kWarningSuffix ); +} +#endif + + +void Material::SetColor (ShaderLab::FastPropertyName name, const ColorRGBAf &col) +{ +#if UNITY_EDITOR + if( IsVectorBuiltinParam(name.GetName()) ) + EmitWarningAboutSettingBuiltinParam(name.GetName()); +#endif + + UnityPropertySheet::ColorMap::iterator i = m_SavedProperties.m_Colors.find (name); + if( i != m_SavedProperties.m_Colors.end() ) + { + #if UNITY_EDITOR + if (i->second.NotEquals(col)) + #endif + { + i->second = col; + SetDirty(); + } + } + + ShaderLab::PropertySheet& properties = GetWritableProperties(); + if (properties.GetColorTag(name)) + properties.SetVector (name, GammaToActiveColorSpace(col).GetPtr()); + else + properties.SetVector (name, col.GetPtr()); + + UpdateHashesOnPropertyChange (name); +} + + +void Material::SetColorIndexed (ShaderLab::FastPropertyName name, int index, float value) +{ +#if UNITY_EDITOR + if( IsVectorBuiltinParam(name.GetName()) ) + EmitWarningAboutSettingBuiltinParam(name.GetName()); +#endif + + UnityPropertySheet::ColorMap::iterator i = m_SavedProperties.m_Colors.find (name); + if( i != m_SavedProperties.m_Colors.end() ) + { + #if UNITY_EDITOR + if (i->second.GetPtr()[index] != value) + #endif + { + i->second.GetPtr()[index] = value; + SetDirty(); + } + } + + ShaderLab::PropertySheet& properties = GetWritableProperties(); + if (properties.GetColorTag(name)) + properties.SetVectorIndexed (name, index, GammaToActiveColorSpace(value)); + else + properties.SetVectorIndexed (name, index, value); + + UpdateHashesOnPropertyChange (name); +} + +ColorRGBAf Material::GetColor (ShaderLab::FastPropertyName name) +{ + EnsurePropertiesExist (); + + const Vector4f* prop = m_Properties->FindVector(name); + if( prop == NULL ) { + AssertStringObject (Format ("Material doesn't have a color property '%s'", name.GetName()), this); + return ColorRGBAf (0,0,0,0); + } + + ColorRGBAf color (prop->x, prop->y, prop->z, prop->w); + if (m_Properties->GetColorTag(name)) + return ActiveToGammaColorSpace (color); + else + return color; +} + + +void Material::SetFloat (ShaderLab::FastPropertyName name, float val) +{ +#if UNITY_EDITOR + if( IsVectorBuiltinParam(name.GetName()) ) + EmitWarningAboutSettingBuiltinParam(name.GetName()); +#endif + + UnityPropertySheet::FloatMap::iterator i = m_SavedProperties.m_Floats.find (name); + if( i != m_SavedProperties.m_Floats.end() ) + { + #if UNITY_EDITOR + if (i->second != val) + #endif + { + i->second = val; + SetDirty(); + } + } + GetWritableProperties().SetFloat (name, val); + + UpdateHashesOnPropertyChange (name); +} + +float Material::GetFloat (ShaderLab::FastPropertyName name) +{ + EnsurePropertiesExist (); + + const float* prop = m_Properties->FindFloat(name); + if( prop == NULL ) { + AssertStringObject (Format ("Material doesn't have a float or range property '%s'", name.GetName()), this); + return 0; + } + return *prop; +} + +bool Material::HasProperty (ShaderLab::FastPropertyName name) +{ + EnsurePropertiesExist (); + return m_Properties->HasProperty(name); +} + + +void Material::ApplyMaterialPropertyDrawers () +{ +# if UNITY_EDITOR + // Don't try to apply property drawers for the very initial Reset() + // that happens; the shader isn't set up yet anyway. + if (!m_Shader.IsValid()) + return; + if (!GetMonoManagerPtr()) + return; + + void* params[] = {Scripting::ScriptingWrapperFor(this)}; + CallStaticMonoMethod ("MaterialEditor", "ApplyMaterialPropertyDrawers", params); +# endif // if UNITY_EDITOR +} + + +#if UNITY_EDITOR +bool Material::ActuallyHasTextureProperty (ShaderLab::FastPropertyName name) const +{ + return m_SavedProperties.m_TexEnvs.find (name) != m_SavedProperties.m_TexEnvs.end(); +} + + +void Material::ResetDefaultTextures(bool overrideSetTextures) +{ + if( !m_Shader ) + return; + + const Shader::DefaultTexturesMap& defaultTextures = m_Shader->GetDefaultTextures(); + for (Shader::DefaultTexturesMap::const_iterator it = defaultTextures.begin(); it != defaultTextures.end(); ++it) + { + bool nullDefaultTexture = it->second.IsNull(); + + for (int i = 0; i < m_Shader->GetPropertyCount (); i++) + { + const ShaderLab::ParserProperty* shaderProperty = m_Shader->GetPropertyInfo (i); + + // only assign if it is a texture where the name and dimensions match + if (shaderProperty->m_Name != it->first.c_str() + || shaderProperty->m_Type != ShaderLab::ParserProperty::kTexture + || (!nullDefaultTexture && shaderProperty->m_DefTexture.m_TexDim != it->second->GetDimension ())) + continue; + + if (!overrideSetTextures) + { + if (GetTexture(ShaderLab::Property(it->first)) != NULL) + continue; + } + + SetTexture (ShaderLab::Property(it->first), it->second); + break; + } + } +} +#endif + + +// Get/Set a matrix value in the material +// We don't allow user editing of matrix props, hence they don't have a representation in the +// UnityPropertySheet, hence we work directly with the SL properties here +void Material::SetMatrix (ShaderLab::FastPropertyName name, const Matrix4x4f &val) +{ +#if UNITY_EDITOR + if( IsMatrixBuiltinParam(name.GetName()) ) + EmitWarningAboutSettingBuiltinParam(name.GetName()); +#endif + + GetWritableProperties().SetValueProp (name, 16, val.GetPtr()); + + UpdateHashesOnPropertyChange (name); +} + +const Matrix4x4f &Material::GetMatrix (ShaderLab::FastPropertyName name) +{ + EnsurePropertiesExist (); + + int count = 0; + const float* prop = m_Properties->GetValueProp (name, &count); + if (prop == NULL || count != 16) + { + AssertStringObject (Format ("Material doesn't have a matrix property '%s'", name.GetName()), this); + return Matrix4x4f::identity; + } + return *reinterpret_cast<const Matrix4x4f*>(prop); +} + + +void Material::SetTexture (ShaderLab::FastPropertyName name, Texture *val) +{ + UnityPropertySheet::TexEnvMap::iterator i = m_SavedProperties.m_TexEnvs.find (name); + if( i != m_SavedProperties.m_TexEnvs.end() ) + { + #if UNITY_EDITOR + if (i->second.m_Texture != PPtr<Texture>(val)) + #endif + { + i->second.m_Texture = val; + SetDirty(); + } + } + else + { + //AssertStringObject (Format ("Material '%s' doesn't have a texture property '%s'", GetName(), name.GetName()), this); + } + + GetWritableProperties().SetTexture (name, val); + + UpdateHashesOnPropertyChange (name); +} + + +Texture *Material::GetTexture (ShaderLab::FastPropertyName name) +{ + EnsurePropertiesExist (); + + UnityPropertySheet::TexEnvMap::const_iterator i = m_SavedProperties.m_TexEnvs.find (name); + if (i == m_SavedProperties.m_TexEnvs.end()) + { + AssertStringObject (Format ("Material doesn't have a texture property '%s'", name.GetName()), this); + return 0; + } + return PPtr<Texture> (i->second.m_Texture); +} + +void Material::SetTextureOffset( ShaderLab::FastPropertyName name, const Vector2f& val ) +{ + UnityPropertySheet::TexEnvMap::iterator i = m_SavedProperties.m_TexEnvs.find (name); + if( i != m_SavedProperties.m_TexEnvs.end() ) + { + #if UNITY_EDITOR + if (i->second.m_Offset != val) + #endif + { + i->second.m_Offset.Set( val.x, val.y ); + SetDirty(); + } + } + GetWritableProperties().SetTextureOffset( name, val.x, val.y ); + + UpdateHashesOnPropertyChange (name); +} + +void Material::SetTextureScale( ShaderLab::FastPropertyName name, const Vector2f& val ) +{ + UnityPropertySheet::TexEnvMap::iterator i = m_SavedProperties.m_TexEnvs.find (name); + if( i != m_SavedProperties.m_TexEnvs.end() ) + { + #if UNITY_EDITOR + if (i->second.m_Scale != val) + #endif + { + i->second.m_Scale.Set( val.x, val.y ); + SetDirty(); + } + } + GetWritableProperties().SetTextureScale( name, val.x, val.y ); + + UpdateHashesOnPropertyChange (name); +} + +void Material::SetTextureScaleAndOffsetIndexed( ShaderLab::FastPropertyName name, int index, float value) +{ + UnityPropertySheet::TexEnvMap::iterator i = m_SavedProperties.m_TexEnvs.find (name); + if( i != m_SavedProperties.m_TexEnvs.end() ) + { + if (index < 2) + { + i->second.m_Scale[index] = value; + } + else + { + i->second.m_Offset[index - 2] = value; + } + SetDirty(); + } + GetWritableProperties().SetTextureScaleAndOffsetIndexed( name, index, value ); + + UpdateHashesOnPropertyChange (name); +} + +Vector2f Material::GetTextureOffset( ShaderLab::FastPropertyName name ) +{ + // get from current runtime sheet + const ShaderLab::TexEnv* texEnv = GetProperties().GetTexEnv (name); + if (texEnv != NULL) + return texEnv->GetTextureOffset (); + + // if not there, get from serialized properties + UnityPropertySheet::TexEnvMap::const_iterator it = m_SavedProperties.m_TexEnvs.find (name); + if (it != m_SavedProperties.m_TexEnvs.end()) + return it->second.m_Offset; + + AssertStringObject (Format ("Material doesn't have a texture property '%s'", name.GetName()), this); + return Vector2f(0,0); +} + +Vector2f Material::GetTextureScale( ShaderLab::FastPropertyName name ) +{ + // get from current runtime sheet + const ShaderLab::TexEnv* texEnv = GetProperties().GetTexEnv (name); + if (texEnv != NULL) + return texEnv->GetTextureScale (); + + // if not there, get from serialized properties + UnityPropertySheet::TexEnvMap::const_iterator it = m_SavedProperties.m_TexEnvs.find (name); + if (it != m_SavedProperties.m_TexEnvs.end()) + return it->second.m_Scale; + + AssertStringObject (Format ("Material doesn't have a texture property '%s'", name.GetName()), this); + return Vector2f(1,1); +} + +void Material::SetComputeBuffer (ShaderLab::FastPropertyName name, ComputeBufferID val) +{ + GetWritableProperties().SetComputeBuffer (name, val); +} + +void Material::CopyPropertiesFromMaterial(Material& other) +{ + m_SavedProperties = other.GetSavedProperties(); + SAFE_RELEASE_LABEL(m_Properties, kMemShader); + { + SET_ALLOC_OWNER(this); + m_Properties = UNITY_NEW(ShaderLab::PropertySheet(other.GetProperties()), kMemShader); + m_Properties->SetOwnerMaterial(this); + } + m_PropertiesDirty = true; + + Shader *shader = m_Shader; + if( !shader ) + shader = Shader::GetDefault(); + + UpdateHashes(); +} + +void Material::ClearProperties () +{ + SAFE_RELEASE_LABEL(m_Properties, kMemShader); + m_PropertiesDirty = true; + m_ShaderUserNode.RemoveFromList(); +} + +int Material::GetRuntimeMemorySize() const +{ int size = Super::GetRuntimeMemorySize(); + if (m_Properties) + size += m_Properties->GetMemoryUsage(); + return size; +} + +void Material::InvalidateDisplayLists() +{ + int subShaderCount = m_CachedSubShaders.size(); + for (int i = 0; i < subShaderCount; i++) + { + CachedSubShader& subshader = m_CachedSubShaders[i]; + int passCount = subshader.passes.size(); + for (int j = 0; j < passCount; j++) + { + CachedShaderPass& pass = subshader.passes[j]; + SAFE_RELEASE(pass.displayList); + } + SAFE_RELEASE(subshader.shadowCasterPass.displayList); + SAFE_RELEASE(subshader.shadowCollectorPass.displayList); + } + m_PropertiesDirty = false; +} + +void Material::BuildShaderKeywordSet () +{ + m_ShaderKeywordSet.Reset (); + for (size_t q = 0; q < m_ShaderKeywords.size (); ++q) + m_ShaderKeywordSet.Enable (keywords::Create (m_ShaderKeywords[q])); +} + +void Material::SetShaderKeywords (const ShaderKeywordsT& keywords) +{ + m_ShaderKeywords = keywords; + BuildShaderKeywordSet (); + UpdateHashes(); + SetDirty(); +} + +void Material::EnableKeyword (const std::string& key) +{ + // already set -> do nothing + if (std::find(m_ShaderKeywords.begin(), m_ShaderKeywords.end(), key) != m_ShaderKeywords.end()) + return; + m_ShaderKeywords.push_back (key); + BuildShaderKeywordSet (); + UpdateHashes(); + SetDirty(); +} + +void Material::DisableKeyword (const std::string& key) +{ + // no keyword -> do nothing + ShaderKeywordsT::iterator it = std::find(m_ShaderKeywords.begin(), m_ShaderKeywords.end(), key); + if (it == m_ShaderKeywords.end()) + return; + + m_ShaderKeywords.erase (it); + BuildShaderKeywordSet (); + UpdateHashes(); + SetDirty(); +} + + +} + +IMPLEMENT_CLASS (Material) +IMPLEMENT_OBJECT_SERIALIZE (Material) +INSTANTIATE_TEMPLATE_TRANSFER (Material) diff --git a/Runtime/Shaders/Material.h b/Runtime/Shaders/Material.h new file mode 100644 index 0000000..46dd954 --- /dev/null +++ b/Runtime/Shaders/Material.h @@ -0,0 +1,233 @@ +#ifndef MATERIAL_H +#define MATERIAL_H + +#include "Runtime/BaseClasses/NamedObject.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Vector4.h" +#include "Runtime/Math/Color.h" +#include "UnityPropertySheet.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Shaders/ShaderKeywords.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Modules/ExportModules.h" + +namespace ShaderLab +{ + class PropertySheet; + struct FastPropertyName; + class ShaderState; + class Pass; +} + +class ChannelAssigns; +class Shader; +class Texture; +class Matrix4x4f; +class GfxDisplayList; + + + +namespace Unity +{ + +class EXPORT_COREMODULE Material : public NamedObject +{ + public: + REGISTER_DERIVED_CLASS (Material, NamedObject) + DECLARE_OBJECT_SERIALIZE (Material) + + Material (MemLabelId label, ObjectCreationMode mode); + // ~Material (); declared-by-macro + virtual void Reset(); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + /// Get how many passes this material requires to render + int GetPassCount (); + + // Set the pass to render next time. + // Calling this function sets up the shader. + // Returns vertex channels used; NULL if this pass should not be rendered. + const ChannelAssigns* SetPass (int passNo, int subshaderIndex = 0, bool allowRecording = true); + + // Set the pass to render next time. + // Can supply a different shader from the one in material, or just cached PPtr deref. + // Also indicates which subshader to use. + // Calling this function sets up the shader. + // Returns vertex channels used; NULL if this pass should not be rendered. + const ChannelAssigns* SetPassWithShader( int passNo, Shader* shader, int subshaderIndex ); + + const ChannelAssigns* SetShadowCasterPass(int subshaderIndex); + const ChannelAssigns* SetShadowCasterPassWithShader(Shader* shader, int subshaderIndex); + const ChannelAssigns* SetShadowCollectorPass(int subshaderIndex); + const ChannelAssigns* SetShadowCollectorPassWithShader(Shader* shader, int subshaderIndex); + + int GetActualRenderQueue() const; + int GetCustomRenderQueue() const { return m_CustomRenderQueue; } + void SetCustomRenderQueue (int q) { if (m_CustomRenderQueue != q) { m_CustomRenderQueue = q; SetDirty(); } } + + inline const ShaderLab::PropertySheet& GetProperties () + { + EnsurePropertiesExist (); + return *m_Properties; + } + + inline void EnsurePropertiesExist () + { +// #if UNITY_EDITOR || WEBPLUG + #if 1 + // Don't preload in the editor because it can easily run out of memory and increase build times. + // In the webplayer we also have some hacks where we call ClearProperties that must be fixed before we can remove the WEBPLUG condition + if( !m_Properties ) + BuildProperties(); + #else + // Enforce that everything is preloaded in the player, reduces hiccups at runtime. + Assert(m_Properties != NULL); + #endif + } + + inline ShaderLab::PropertySheet& GetWritableProperties () + { + EnsurePropertiesExist (); + SetPropertiesDirty(); + return *m_Properties; + } + + void SetPropertiesDirty () + { + m_PropertiesDirty = true; + } + + /// Clears the cached properties. + /// This is used in the web player to avoid caching issues with builtin resources when reloading the player. + void ClearProperties (); + + // Sets the materials properties + void CopyPropertiesFromMaterial(Material& other); + + // Get/Set a color value in the material + void SetColor (ShaderLab::FastPropertyName name, const ColorRGBAf &col); + void SetColorIndexed (ShaderLab::FastPropertyName name, int indexed, float value); + ColorRGBAf GetColor (ShaderLab::FastPropertyName name); + + // Get/Set a float value in the material + void SetFloat (ShaderLab::FastPropertyName name, float val); + float GetFloat (ShaderLab::FastPropertyName name); + + // Get/Set a matrix value in the material + void SetMatrix ( ShaderLab::FastPropertyName name, const Matrix4x4f &val); + const Matrix4x4f &GetMatrix (ShaderLab::FastPropertyName name); + + // Get/Set a texture value in the material + void SetTexture (ShaderLab::FastPropertyName name, Texture *val); + Texture *GetTexture (ShaderLab::FastPropertyName name); + + bool HasProperty (ShaderLab::FastPropertyName name); + + // Texture placement + void SetTextureOffset( ShaderLab::FastPropertyName name, const Vector2f& offset ); + void SetTextureScale( ShaderLab::FastPropertyName name, const Vector2f& scale ); + + Vector2f GetTextureOffset( ShaderLab::FastPropertyName name ); + Vector2f GetTextureScale( ShaderLab::FastPropertyName name ); + + void SetTextureScaleAndOffsetIndexed (ShaderLab::FastPropertyName name, int indexed, float value); + + void SetComputeBuffer (ShaderLab::FastPropertyName name, ComputeBufferID val); + + + // Get the default material + static Material *GetDefault (); + static Material *GetDefaultDiffuseMaterial (); + + static Material *CreateMaterial (const char *shaderStr, int hideFlags, bool scriptingObjectIsBeingCreated = false); + static Material *CreateMaterial (Shader& shader, int hideFlags, bool scriptingObjectIsBeingCreated = false); + static Material *CreateMaterial (const Material& material, int hideFlags, bool scriptingObjectIsBeingCreated = false); + + // Set the shader that drives the material + void SetShader (Shader *s); + + const Shader *GetShader() const; + Shader *GetShader(); + PPtr<Shader> GetShaderPPtr() const; + + std::string GetTag( const string& tag, bool currentSubShaderOnly, const string& defaultValue ) const; + + typedef std::vector<UnityStr> ShaderKeywordsT; + const ShaderKeywordsT& GetShaderKeywords () const { return m_ShaderKeywords; } + void SetShaderKeywords (const ShaderKeywordsT& keywords); + ShaderKeywordSet GetShaderKeywordSet () const { return m_ShaderKeywordSet; } + void EnableKeyword (const std::string& key); + void DisableKeyword (const std::string& key); + void ApplyMaterialPropertyDrawers(); + + #if UNITY_EDITOR + bool ActuallyHasTextureProperty (ShaderLab::FastPropertyName name) const; + + void ResetDefaultTextures (bool overrideSetTextures); + #endif + + inline UnityPropertySheet& GetSavedProperties () { return m_SavedProperties; } + inline const UnityPropertySheet& GetSavedProperties () const { return m_SavedProperties; } + + // If m_Owner equals renderer returns this + // Otherwise creates a copy and sets m_Owner to renderer. + // Used by Animation system and scripting to animate material properties of a single renderer. + static Material& GetInstantiatedMaterial (Material* material, Object& renderer, bool allowInEditMode); + PPtr<Object> GetOwner () { return m_Owner; } + + virtual int GetRuntimeMemorySize () const; + + void InvalidateDisplayLists (); + + inline UInt32 GetShadowCasterHash() { EnsurePropertiesExist (); return m_ShadowCasterHash; } + inline UInt32 GetShadowCollectorHash() { EnsurePropertiesExist (); return m_ShadowCollectorHash; } + +private: + /// Build the ShaderLab property sheet (m_Properties) from the saved properties. + /// This will correctly handle default properties supplied by the shader... + void BuildProperties (); + void BuildShaderKeywordSet (); + + void UpdateHashesOnPropertyChange (ShaderLab::FastPropertyName name); + void UpdateHashes (); + + struct CachedShaderPass + { + CachedShaderPass() : displayList(NULL), channelAssigns(NULL), shaderKeywords(0), globalFogMode(kFogDisabled) {} + + GfxDisplayList* displayList; + const ChannelAssigns* channelAssigns; + UInt64 shaderKeywords; + FogMode globalFogMode; + }; + + struct CachedSubShader + { + dynamic_array<CachedShaderPass> passes; + CachedShaderPass shadowCasterPass; + CachedShaderPass shadowCollectorPass; + }; + + PPtr<Shader> m_Shader; + ShaderLab::PropertySheet* m_Properties; + bool m_PropertiesDirty; + std::vector<CachedSubShader> m_CachedSubShaders; + int m_CustomRenderQueue; // -1 if should use shader's + PPtr<Object> m_Owner; + UnityPropertySheet m_SavedProperties; + + ListNode<Material> m_ShaderUserNode; + ShaderKeywordsT m_ShaderKeywords; + ShaderKeywordSet m_ShaderKeywordSet; + UInt32 m_ShadowCollectorHash; + UInt32 m_ShadowCasterHash; + UInt32 m_StateKeyHash; +}; + +} + +using namespace Unity; + +#endif diff --git a/Runtime/Shaders/MaterialIsTransparent.h b/Runtime/Shaders/MaterialIsTransparent.h new file mode 100644 index 0000000..072b054 --- /dev/null +++ b/Runtime/Shaders/MaterialIsTransparent.h @@ -0,0 +1,14 @@ +#include "Material.h" + +inline bool IsTransparentOrCutoutMaterial (Material& material) +{ + string tag = material.GetTag("RenderType", false, string()); + + if (material.GetActualRenderQueue() >= kAlphaTestRenderQueue) + return true; + + if (tag == "TreeTransparentCutout" || tag == "GrassBillboard" || tag == "Grass" || tag == "TreeLeaf" || tag == "TranparentCutout" || tag == "Tranparent") + return true; + + return false; +} diff --git a/Runtime/Shaders/MaterialProperties.cpp b/Runtime/Shaders/MaterialProperties.cpp new file mode 100644 index 0000000..357a73f --- /dev/null +++ b/Runtime/Shaders/MaterialProperties.cpp @@ -0,0 +1,195 @@ +#include "UnityPrefix.h" +#include "MaterialProperties.h" +#include "Material.h" +#include "External/shaderlab/Library/properties.h" +#include "Runtime/Math/Matrix4x4.h" + +inline size_t CalculateSizeFromProperty (const MaterialPropertyBlock::Property& prop) +{ + return prop.rows * prop.cols * prop.arraySize; +} + +MaterialPropertyBlock::MaterialPropertyBlock(Property* props, size_t propCount, float* buffer, size_t bufSize) +{ + m_Properties.assign_external(props, props + propCount); + m_Buffer.assign_external(buffer, buffer + bufSize); +} + +void MaterialPropertyBlock::AddProperty(const ShaderLab::FastPropertyName& name, const float* data, UInt8 rows, UInt8 cols, size_t arraySize) +{ + size_t offset = m_Buffer.size(); + Property prop = { name.index, rows, cols, kTexDimNone, arraySize, offset }; + m_Properties.push_back(prop); + size_t size = rows * cols * arraySize; + m_Buffer.resize_uninitialized(offset + size); + memcpy(&m_Buffer[offset], data, size * sizeof(float)); +} + +void MaterialPropertyBlock::AddPropertyTexture(const ShaderLab::FastPropertyName& name, TextureDimension dim, TextureID tid) +{ + AddProperty (name, reinterpret_cast<float*> (&tid.m_ID), 1, 1, 1); + m_Properties.back().texDim = dim; +} + +void MaterialPropertyBlock::AddPropertyFloat(const ShaderLab::FastPropertyName& name, float val) +{ + AddProperty(name, &val, 1, 1, 1); +} + +void MaterialPropertyBlock::AddPropertyVector(const ShaderLab::FastPropertyName& name, const Vector4f& vec) +{ + AddProperty(name, vec.GetPtr(), 1, 4, 1); +} + +void MaterialPropertyBlock::AddPropertyColor(const ShaderLab::FastPropertyName& name, const ColorRGBAf& col) +{ + ColorRGBAf converted = GammaToActiveColorSpace (col); + AddProperty(name, converted.GetPtr(), 1, 4, 1); +} + +void MaterialPropertyBlock::AddPropertyMatrix(const ShaderLab::FastPropertyName& name, const Matrix4x4f& mat) +{ + AddProperty(name, mat.GetPtr(), 4, 4, 1); +} + +void MaterialPropertyBlock::ReplacePropertyTexture(const ShaderLab::FastPropertyName& name, TextureDimension dim, TextureID tid) +{ + int index = GetPropertyIndex(name); + if (index == -1) + AddPropertyTexture(name, dim, tid); + else + { + Property& prop = m_Properties[index]; + if (prop.rows == 1 && prop.cols == 1 && prop.arraySize == 1) + { + TextureID* buf = reinterpret_cast<TextureID*> (&m_Buffer[prop.offset]); + *buf = tid; + prop.texDim = dim; + } + else + { + ErrorString("The material property is different from already stored property."); + } + } +} + +void MaterialPropertyBlock::ReplacePropertyColor(const ShaderLab::FastPropertyName& name, const ColorRGBAf& col) +{ + ColorRGBAf activeColor = GammaToActiveColorSpace (col); + ReplacePropertyVector (name, *reinterpret_cast<const Vector4f*> (&activeColor)); +} + +void MaterialPropertyBlock::ReplacePropertyVector(const ShaderLab::FastPropertyName& name, const Vector4f& vec) +{ + int index = GetPropertyIndex(name); + if (index == -1) + AddPropertyVector(name, vec); + else + { + const Property& prop = m_Properties[index]; + if (prop.rows == 1 && prop.cols == 4 && prop.arraySize == 1) + { + Vector4f* buf = reinterpret_cast<Vector4f*> (&m_Buffer[prop.offset]); + *buf = vec; + } + else + { + ErrorString("The material property is different from already stored property."); + } + } +} + +void MaterialPropertyBlock::ReplacePropertyFloat(const ShaderLab::FastPropertyName& name, float data) +{ + ReplacePartialFloatProperty(name, data, 1, 0); +} + +void MaterialPropertyBlock::ReplacePartialFloatColorProperty(const ShaderLab::FastPropertyName& name, float data, UInt8 cols, UInt8 colIndex) +{ + ReplacePartialFloatProperty(name, GammaToActiveColorSpace(data), cols, colIndex); +} + +void MaterialPropertyBlock::ReplacePartialFloatProperty(const ShaderLab::FastPropertyName& name, float data, UInt8 cols, UInt8 colIndex) +{ + int index = GetPropertyIndex(name); + if (index == -1) + { + float prop[4] = { 0.0F, 0.0F, 0.0F, 0.0F }; + prop[colIndex] = data; + + AddProperty(name, prop, 1, cols, 1); + } + else + { + const Property& prop = m_Properties[index]; + if (prop.rows == 1 && prop.cols == cols && prop.arraySize == 1) + { + float* buffer = reinterpret_cast<float*> (&m_Buffer[prop.offset]); + buffer[colIndex] = data; + } + else + { + ErrorString("The material property is different from already stored property."); + } + } +} + +int MaterialPropertyBlock::GetPropertyIndex (const ShaderLab::FastPropertyName& name) const +{ + for (int i=0;i<m_Properties.size();i++) + { + if (m_Properties[i].nameIndex == name.index) + return i; + } + return -1; +} + +const void* MaterialPropertyBlock::Find(const ShaderLab::FastPropertyName& name, UInt8 rows, UInt8 cols, size_t arraySize) const +{ + for (size_t i = 0, n = m_Properties.size(); i != n; ++i) + { + const Property& prop = m_Properties[i]; + if (name.index == prop.nameIndex && prop.cols == cols && prop.rows == rows) + return &m_Buffer[prop.offset]; + } + return NULL; +} + + +const float* MaterialPropertyBlock::FindFloat(const ShaderLab::FastPropertyName& name) const +{ + return static_cast<const float*> (Find(name, 1, 1, 1)); +} + +const Vector4f* MaterialPropertyBlock::FindVector(const ShaderLab::FastPropertyName& name) const +{ + return static_cast<const Vector4f*> (Find(name, 1, 4, 1)); +} + +const Matrix4x4f* MaterialPropertyBlock::FindMatrix(const ShaderLab::FastPropertyName& name) const +{ + return static_cast<const Matrix4x4f*> (Find(name, 4, 4, 1)); +} + +bool MaterialPropertyBlock::GetColor(const ShaderLab::FastPropertyName& name, ColorRGBAf& outColor) const +{ + const ColorRGBAf* color = static_cast<const ColorRGBAf*> (Find(name, 1, 4, 1)); + if (color != NULL) + { + outColor = ActiveToGammaColorSpace(*color); + return true; + } + else + return false; +} + +const TextureID MaterialPropertyBlock::FindTexture(const ShaderLab::FastPropertyName& name) const +{ + for (size_t i = 0, n = m_Properties.size(); i != n; ++i) + { + const Property& prop = m_Properties[i]; + if (name.index == prop.nameIndex && prop.texDim != kTexDimNone) + return TextureID(*(const int*)&m_Buffer[prop.offset]); + } + return TextureID(); +} diff --git a/Runtime/Shaders/MaterialProperties.h b/Runtime/Shaders/MaterialProperties.h new file mode 100644 index 0000000..5c1c0bd --- /dev/null +++ b/Runtime/Shaders/MaterialProperties.h @@ -0,0 +1,85 @@ +#ifndef MATERIAL_PROPERTIES_H +#define MATERIAL_PROPERTIES_H + +#include "Runtime/Utilities/dynamic_array.h" +#include "Runtime/Modules/ExportModules.h" +#include "Runtime/GfxDevice/GfxDeviceTypes.h" + +class Vector4f; +class Matrix4x4f; +class ColorRGBAf; +namespace Unity { class Material; } +namespace ShaderLab { struct FastPropertyName; } + +// Tightly packed buffer of material properties +class EXPORT_COREMODULE MaterialPropertyBlock +{ +public: + struct Property + { + int nameIndex; + UInt8 rows; + UInt8 cols; + UInt8 texDim; // if texDim==None, this is a value property + // These should not be size_t, as the GfxDevice may run across processes of different + // bitness, and the data serialized in the command buffer must match. + UInt32 arraySize; + UInt32 offset; + }; + + MaterialPropertyBlock() {} + + // Does not copy data! + MaterialPropertyBlock(Property* props, size_t propCount, float* buffer, size_t bufSize); + + // Clear all properties + void Clear(); + + // Add Properties without checking if another property with the same name exists + void AddProperty(const ShaderLab::FastPropertyName& name, const float* data, UInt8 rows, UInt8 cols, size_t arraySize); + void AddPropertyFloat(const ShaderLab::FastPropertyName& name, float val); + void AddPropertyVector(const ShaderLab::FastPropertyName& name, const Vector4f& vec); + void AddPropertyColor(const ShaderLab::FastPropertyName& name, const ColorRGBAf& col); + void AddPropertyMatrix(const ShaderLab::FastPropertyName& name, const Matrix4x4f& mat); + void AddPropertyTexture(const ShaderLab::FastPropertyName& name, TextureDimension dim, TextureID tid); + + // Replace properties + void ReplacePropertyFloat(const ShaderLab::FastPropertyName& name, float data); + void ReplacePropertyVector(const ShaderLab::FastPropertyName& name, const Vector4f& col); + void ReplacePropertyColor(const ShaderLab::FastPropertyName& name, const ColorRGBAf& col); + void ReplacePropertyTexture(const ShaderLab::FastPropertyName& name, TextureDimension dim, TextureID tid); + + // Replaces a single float property on either a float1 or one component of a float4. + /// If other components on a float4 are not yet defined, they will be initialized to zero + void ReplacePartialFloatProperty(const ShaderLab::FastPropertyName& name, float data, UInt8 cols, UInt8 colIndex); + void ReplacePartialFloatColorProperty(const ShaderLab::FastPropertyName& name, float data, UInt8 cols, UInt8 colIndex); + + + const float* FindFloat(const ShaderLab::FastPropertyName& name) const; + const Vector4f* FindVector(const ShaderLab::FastPropertyName& name) const; + bool GetColor(const ShaderLab::FastPropertyName& name, ColorRGBAf& outColor) const; + const Matrix4x4f* FindMatrix(const ShaderLab::FastPropertyName& name) const; + const TextureID FindTexture(const ShaderLab::FastPropertyName& name) const; + + const Property* GetPropertiesBegin() const { return m_Properties.begin(); } + const Property* GetPropertiesEnd() const { return m_Properties.end(); } + const float* GetBufferBegin() const { return m_Buffer.begin(); } + const float* GetBufferEnd() const { return m_Buffer.end(); } + + + const void* Find(const ShaderLab::FastPropertyName& name, UInt8 rows, UInt8 cols, size_t arraySize) const; + int GetPropertyIndex (const ShaderLab::FastPropertyName& name) const; + +private: + dynamic_array<Property> m_Properties; + dynamic_array<float> m_Buffer; +}; + +inline void MaterialPropertyBlock::Clear() +{ + m_Properties.resize_uninitialized(0); + m_Buffer.resize_uninitialized(0); +} + + +#endif diff --git a/Runtime/Shaders/NameToObjectMap.h b/Runtime/Shaders/NameToObjectMap.h new file mode 100644 index 0000000..6c63f22 --- /dev/null +++ b/Runtime/Shaders/NameToObjectMap.h @@ -0,0 +1,153 @@ +#ifndef NAMETOOBJECTMAP_H +#define NAMETOOBJECTMAP_H + +#include <string> +#include <map> +#include "Runtime/BaseClasses/BaseObject.h" +#if UNITY_EDITOR +#include "Editor/Src/BuildPipeline/BuildSerialization.h" +#endif + +using std::map; +using std::string; +using std::pair; + + +static inline bool IsBuiltinResourceObject(Object* o) +{ + // built-in resources have this flag set + if (o->TestHideFlag(Object::kHideAndDontSave)) + return true; + + // Resources from builtin_extra only have "not editable" flag set, so the above check doesn't catch them. + // In the editor (where this matters mostly when building resource files), catch that by detecting + // if the object came from any built-in resources file. + #if UNITY_EDITOR + if (IsAnyDefaultResourcesObject(o->GetInstanceID())) + return true; + #endif + + return false; +} + +template<class Type, class ObjectToName, class NameToObject> +class NameToObjectMap +{ +private: + typedef typename ObjectToName::iterator ObjectToNameIterator; + typedef typename NameToObject::iterator NameToObjectIterator; + + ObjectToName m_ObjectToName; + NameToObject m_NameToObject; + Object* m_ObjectToDirty; + +public: + + DECLARE_SERIALIZE (NameToObjectMap) + + void SetObjectToDirty (Object* dirty) { m_ObjectToDirty = dirty; } + + void Add (const string& name, PPtr<Type> o) + { + Remove (o); + m_ObjectToName.insert (make_pair (o, name)); + m_NameToObject.insert (make_pair (name, o)); + AssertIf (m_NameToObject.size () != m_ObjectToName.size ()); + m_ObjectToDirty->SetDirty (); + } + + bool Remove (PPtr<Type> object) + { + AssertIf (m_NameToObject.size () != m_ObjectToName.size ()); + int oldSize = m_NameToObject.size (); + { + pair<NameToObjectIterator, NameToObjectIterator> range = make_pair(m_NameToObject.begin(), m_NameToObject.end()); + + NameToObjectIterator i, next; + for (i=range.first;i!=range.second;i=next) + { + next = i; next++; + if (i->second == object) + { + m_NameToObject.erase (i); + } + } + } + + { + pair<ObjectToNameIterator, ObjectToNameIterator> range; + range = m_ObjectToName.equal_range (object); + m_ObjectToName.erase(range.first, range.second); + } + + m_ObjectToDirty->SetDirty (); + AssertIf (m_NameToObject.size () != m_ObjectToName.size ()); + return oldSize != m_NameToObject.size (); + } + + Type* Find (const string& name) + { + // Get all with name 'name' + pair<NameToObjectIterator, NameToObjectIterator> range; + range = m_NameToObject.equal_range (name); + NameToObjectIterator i, next; + Type* found = NULL; + // Then find the first that is loaded, those that can't be loaded + // are removed. + for (i=range.first;i!=range.second;i=next) + { + next = i; next++; + Type* o = i->second; + + if (o) + { + // When there are two shaders one builtin resource and one normal shader + // Then we want the one in the project folder not the builtin one. So people can override shaders + // At some point we should try to get the ordering of shader includes better defined! + if (found && IsBuiltinResourceObject(o)) + continue; + + found = o; + } + } + + return found; + } + + std::vector<PPtr<Type> > GetAllObjects () + { + std::vector<PPtr<Type> > objects; + for (NameToObjectIterator i=m_NameToObject.begin ();i!=m_NameToObject.end ();i++) + { + objects.push_back(i->second); + } + return objects; + } + + const NameToObject& GetAll () + { + return m_NameToObject; + } + +private: + + void Rebuild () + { + // Rebuild name -> object + m_NameToObject.clear (); + ObjectToNameIterator i; + for (i=m_ObjectToName.begin ();i != m_ObjectToName.end ();i++) + m_NameToObject.insert (make_pair (i->second, i->first)); + } +}; + +template<class Type, class ObjectToName, class NameToObject> +template<class TransferFunction> +void NameToObjectMap<Type, ObjectToName, NameToObject>::Transfer (TransferFunction& transfer) +{ + TRANSFER (m_ObjectToName); + if (transfer.IsReading ()) + Rebuild (); +} + +#endif diff --git a/Runtime/Shaders/Shader.cpp b/Runtime/Shaders/Shader.cpp new file mode 100644 index 0000000..b33aa00 --- /dev/null +++ b/Runtime/Shaders/Shader.cpp @@ -0,0 +1,734 @@ +#include "UnityPrefix.h" +#include "Shader.h" +#include "External/shaderlab/Library/intshader.h" +#include "External/shaderlab/Library/properties.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "Material.h" +#include "ShaderNameRegistry.h" +#include "Runtime/Utilities/Word.h" +#include "External/shaderlab/Library/ShaderParser.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Misc/ResourceManager.h" +#include "GraphicsCaps.h" +#include "External/shaderlab/Library/SLParserData.h" + +#if UNITY_EDITOR +#include "External/shaderlab/Library/ShaderWriter.h" +#endif +#include "Runtime/Profiler/MemoryProfiler.h" + +using std::vector; + +// To get a precompiled version of specific platform shader +// Create this shader in UnityEditor and click on "Open compiled shader" +//Shader "DefaultShader" +//{ +// SubShader +// { +// Pass +// { +// CGPROGRAM +// +// #pragma vertex vert +// #pragma fragment frag +// float4 vert (float4 pos : POSITION) : SV_POSITION { return mul(UNITY_MATRIX_MVP,pos); } +// float4 frag () : COLOR { return float4(1,0,1,1); } +// ENDCG +// } +// } +//} + + + +static const char *gDefaultString = +"Shader \"Default\" {" +#if UNITY_XENON +"SubShader { Pass { Cull Off \n" +"Program \"vp\" { SubProgram \"xbox360 \" { Keywords { }\n" +" Bind \"vertex\" Vertex\n" +" Matrix 0 [glstate_matrix_mvp]\n" +" \"vs_360\n" +" backbbabaaaaaalmaaaaaagmaaaaaaaaaaaaaaceaaaaaaaaaaaaaajaaaaaaaaa\n" +" aaaaaaaaaaaaaagiaaaaaabmaaaaaaflpppoadaaaaaaaaabaaaaaabmaaaaaaaa\n" +" aaaaaafeaaaaaadaaaacaaaaaaaeaaaaaaaaaaeeaaaaaaaaghgmhdhegbhegffp\n" +" gngbhehcgjhifpgnhghaaaklaaadaaadaaaeaaaeaaabaaaaaaaaaaaahghdfpdd\n" +" fpdaaadccodacodjdddcdicodaaaklklaaaaaaaaaaaaaagmaaabaaabaaaaaaaa\n" +" aaaaaaaaaaaaaaaaaaaaaaabaaaaaaabaaaaaaaaaaaaacjaaaaaaaadbaabbaad\n" +" aaaabcaamcaaaaaaaaaaeaaeaaaabcaameaaaaaaaaaaaaadaaaaccaaaaaaaaaa\n" +" afpibaaaaaaaagiiaaaaaaaamiapaaaaaabliiaakbabadaamiapaaaaaamgiiaa\n" +" klabacaamiapaaaaaalbdejeklababaamiapiadoaagmaadeklabaaaaaaaaaaaa\n" +" aaaaaaaaaaaaaaaa\"\n" +"}}\n" +"Program \"fp\" { SubProgram \"xbox360 \" { Keywords { }\n" +"\"ps_360\n" +" backbbaaaaaaaakaaaaaaageaaaaaaaaaaaaaaceaaaaaafiaaaaaaiaaaaaaaaa\n" +" aaaaaaaaaaaaaadaaaaaaabmaaaaaacdppppadaaaaaaaaaaaaaaaaaaaaaaaaaa\n" +" aaaaaabmhahdfpddfpdaaadccodacodjdddcdicodaaaklklaaaaaaaaaaaaaaab\n" +" aaaaaaaaaaaaaaaaaaaaaabeabpmaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" +" aaaaaaeaaaaaaacebaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab\n" +" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" +" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadpiaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" +" aaaaaaaabaabmeaaccaaaaaabenmmaaaaaaaaagmmcaaaappaaaaaaaaaaaaaaaa\n" +" aaaaaaaa\"\n" +"}}\n" +"}}\n" +#elif UNITY_PS3 +"SubShader { Pass { Cull Off \n" +"Program \"vp\" { SubProgram \"ps3 \" { Keywords { }\n" +"Matrix 256 [glstate_matrix_mvp]\n" +"Bind \"vertex\" Vertex\n" +"\"sce_vp_rsx // 4 instructions using 1 registers\n" +"[Configuration]\n" +"8\n" +"0000000400010100\n" +"[Microcode]\n" +"64\n" +"401f9c6c01d0300d8106c0c360403f80401f9c6c01d0200d8106c0c360405f80\n" +"401f9c6c01d0100d8106c0c360409f80401f9c6c01d0000d8106c0c360411f81\n" +"\"\n" +"}}\n" +"Program \"fp\" { SubProgram \"ps3 \" { Keywords { }\n" +"\"sce_fp_rsx // 2 instructions using 2 registers\n" +"[Configuration]\n" +"24\n" +"ffffffff000000200000ffff000000000000840002000000\n" +"[Microcode]\n" +"32\n" +"1e81014008021c9cc8000001c800000100003f80000000000000000000000000\n" +"\"\n" +"}}\n" +"}}\n" + +#else + +#if GFX_SUPPORTS_D3D11 +"SubShader { Pass {\n" +"Program \"vp\" {\n" +"SubProgram \"d3d11 \" { Bind \"vertex\" Vertex ConstBuffer \"UnityPerDraw\" 336 Matrix 0 [glstate_matrix_mvp] 4 BindCB \"UnityPerDraw\" 0\n" +"\"vs_dx11\n" +"eefiecedijhpljdppnfhjnjaadaickkmhicpkjbcabaaaaaaheabaaaaadaaaaaa\n" +"cmaaaaaagaaaaaaajeaaaaaaejfdeheocmaaaaaaabaaaaaaaiaaaaaacaaaaaaa\n" +"aaaaaaaaaaaaaaaaadaaaaaaaaaaaaaaapapaaaafaepfdejfeejepeoaaklklkl\n" +"epfdeheocmaaaaaaabaaaaaaaiaaaaaacaaaaaaaaaaaaaaaabaaaaaaadaaaaaa\n" +"aaaaaaaaapaaaaaafdfgfpfaepfdejfeejepeoaafdeieefcniaaaaaaeaaaabaa\n" +"dgaaaaaafjaaaaaeegiocaaaaaaaaaaaaeaaaaaafpaaaaadpcbabaaaaaaaaaaa\n" +"ghaaaaaepccabaaaaaaaaaaaabaaaaaagiaaaaacabaaaaaadiaaaaaipcaabaaa\n" +"aaaaaaaafgbfbaaaaaaaaaaaegiocaaaaaaaaaaaabaaaaaadcaaaaakpcaabaaa\n" +"aaaaaaaaegiocaaaaaaaaaaaaaaaaaaaagbabaaaaaaaaaaaegaobaaaaaaaaaaa\n" +"dcaaaaakpcaabaaaaaaaaaaaegiocaaaaaaaaaaaacaaaaaakgbkbaaaaaaaaaaa\n" +"egaobaaaaaaaaaaadcaaaaakpccabaaaaaaaaaaaegiocaaaaaaaaaaaadaaaaaa\n" +"pgbpbaaaaaaaaaaaegaobaaaaaaaaaaadoaaaaab\"\n" +"}\n" +"SubProgram \"d3d11_9x \" {\n" +"Bind \"vertex\" Vertex\n" +"ConstBuffer \"UnityPerDraw\" 336 \n" +"Matrix 0 [glstate_matrix_mvp] 4\n" +"BindCB \"UnityPerDraw\" 0\n" +"\"vs_4_0_level_9_1\n" +"eefieceddggiiplcpkoeljnckhkjahapknjdfpkhabaaaaaadeacaaaaaeaaaaaa\n" +"daaaaaaaomaaaaaammabaaaaaaacaaaaebgpgodjleaaaaaaleaaaaaaaaacpopp\n" +"iaaaaaaadeaaaaaaabaaceaaaaaadaaaaaaadaaaaaaaceaaabaadaaaaaaaaaaa\n" +"aeaaabaaaaaaaaaaaaaaaaaaaaacpoppbpaaaaacafaaaaiaaaaaapjaafaaaaad\n" +"aaaaapiaaaaaffjaacaaoekaaeaaaaaeaaaaapiaabaaoekaaaaaaajaaaaaoeia\n" +"aeaaaaaeaaaaapiaadaaoekaaaaakkjaaaaaoeiaaeaaaaaeaaaaapiaaeaaoeka\n" +"aaaappjaaaaaoeiaaeaaaaaeaaaaadmaaaaappiaaaaaoekaaaaaoeiaabaaaaac\n" +"aaaaammaaaaaoeiappppaaaafdeieefcniaaaaaaeaaaabaadgaaaaaafjaaaaae\n" +"egiocaaaaaaaaaaaaeaaaaaafpaaaaadpcbabaaaaaaaaaaaghaaaaaepccabaaa\n" +"aaaaaaaaabaaaaaagiaaaaacabaaaaaadiaaaaaipcaabaaaaaaaaaaafgbfbaaa\n" +"aaaaaaaaegiocaaaaaaaaaaaabaaaaaadcaaaaakpcaabaaaaaaaaaaaegiocaaa\n" +"aaaaaaaaaaaaaaaaagbabaaaaaaaaaaaegaobaaaaaaaaaaadcaaaaakpcaabaaa\n" +"aaaaaaaaegiocaaaaaaaaaaaacaaaaaakgbkbaaaaaaaaaaaegaobaaaaaaaaaaa\n" +"dcaaaaakpccabaaaaaaaaaaaegiocaaaaaaaaaaaadaaaaaapgbpbaaaaaaaaaaa\n" +"egaobaaaaaaaaaaadoaaaaabejfdeheocmaaaaaaabaaaaaaaiaaaaaacaaaaaaa\n" +"aaaaaaaaaaaaaaaaadaaaaaaaaaaaaaaapapaaaafaepfdejfeejepeoaaklklkl\n" +"epfdeheocmaaaaaaabaaaaaaaiaaaaaacaaaaaaaaaaaaaaaabaaaaaaadaaaaaa\n" +"aaaaaaaaapaaaaaafdfgfpfaepfdejfeejepeoaa\"\n" +"}\n" +"}\n" +"Program \"fp\" {\n" +"SubProgram \"d3d11 \" {\n" +"\"ps_dx11\n" +"eefiecedlfbokalingbkfpbcgbelaibibjjpegjnabaaaaaalaaaaaaaadaaaaaa\n" +"cmaaaaaadmaaaaaahaaaaaaaejfdeheoaiaaaaaaaaaaaaaaaiaaaaaaepfdeheo\n" +"cmaaaaaaabaaaaaaaiaaaaaacaaaaaaaaaaaaaaaaaaaaaaaadaaaaaaaaaaaaaa\n" +"apaaaaaafdfgfpfegbhcghgfheaaklklfdeieefcdiaaaaaaeaaaaaaaaoaaaaaa\n" +"gfaaaaadpccabaaaaaaaaaaadgaaaaaipccabaaaaaaaaaaaaceaaaaaaaaaiadp\n" +"aaaaaaaaaaaaiadpaaaaiadpdoaaaaab\"\n" +"}\n" +"SubProgram \"d3d11_9x \" {\n" +"\"ps_4_0_level_9_1\n" +"eefiecednclpehpkkhodapchnemhifoohfkahfmoabaaaaaaamabaaaaaeaaaaaa\n" +"daaaaaaaiiaaaaaamiaaaaaaniaaaaaaebgpgodjfaaaaaaafaaaaaaaaaacpppp\n" +"cmaaaaaaceaaaaaaaaaaceaaaaaaceaaaaaaceaaaaaaceaaaaaaceaaaaacpppp\n" +"fbaaaaafaaaaapkaaaaaiadpaaaaaaaaaaaaiadpaaaaiadpabaaaaacaaaiapia\n" +"aaaaoekappppaaaafdeieefcdiaaaaaaeaaaaaaaaoaaaaaagfaaaaadpccabaaa\n" +"aaaaaaaadgaaaaaipccabaaaaaaaaaaaaceaaaaaaaaaiadpaaaaaaaaaaaaiadp\n" +"aaaaiadpdoaaaaabejfdeheoaiaaaaaaaaaaaaaaaiaaaaaaepfdeheocmaaaaaa\n" +"abaaaaaaaiaaaaaacaaaaaaaaaaaaaaaaaaaaaaaadaaaaaaaaaaaaaaapaaaaaa\n" +"fdfgfpfegbhcghgfheaaklkl\"\n" +"}\n" +"}\n" +"}}" +#endif + +" SubShader { Tags { \"ForceSupported\" = \"True\" } Pass{ Cull Off SetTexture[_Dummy] { constantColor (1,0,1,1) combine constant }}} \n" +#endif +"}"; + +int* gShaderLabContainer = NULL; + +static ShaderLab::IntShader* gDefaultShaderLabShader = NULL; +static Shader* gDefaultShader = NULL; +static Shader* s_ClearShader = NULL; +static void MakeDefaultShaderLabShader (); + +Shader::Shader(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_ShaderIsBaked(false) +{ + MakeDefaultShaderLabShader (); + m_Shader = gDefaultShaderLabShader; + m_ShadowCasterPass = NULL; + m_ShadowCollectorPass = NULL; + m_NeedsParsing = false; + + #if UNITY_EDITOR + m_ParsedForm = NULL; + #endif + + if (mode != kCreateObjectFromNonMainThread) + PostLoad (); +} + +Shader *Shader::GetDefault () +{ + if (!gDefaultShader) { + SET_ALLOC_OWNER(gShaderLabContainer); + gDefaultShader = NEW_OBJECT(Shader); + gDefaultShader->Reset(); + #if !UNITY_RELEASE + // in Shader's AwakeFromLoad it will try to parse script and call PostLoad + // we basically doing the same, but we assign default shader directly (w/o script) + gDefaultShader->HackSetAwakeWasCalled(); + #endif + + gDefaultShader->m_Shader = gDefaultShaderLabShader; + gDefaultShader->PostLoad(); + gDefaultShader->SetHideFlags (kHideAndDontSave); + } + return gDefaultShader; +} + +Shader* Shader::GetScreenClearShader () +{ + Assert (s_ClearShader); + return s_ClearShader; +} + + +void Shader::LoadDefaultShaders () +{ + GetDefault (); + + #if GFX_SUPPORTS_D3D11 || GFX_SUPPORTS_XENON + if (IsGfxDevice()) + { + Assert (!s_ClearShader); + s_ClearShader = GetBuiltinResource<Shader> ("Internal-Clear.shader"); + } + #endif +} + + +Shader::~Shader () +{ + if (m_Shader != gDefaultShaderLabShader) + UNITY_DELETE(m_Shader, kMemShader); + + if (this == gDefaultShader) + gDefaultShader = NULL; + + #if UNITY_EDITOR + UNITY_DELETE( m_ParsedForm, kMemShader); + #endif +} + +bool Shader::MainThreadCleanup () +{ + if (m_Shader != gDefaultShaderLabShader) + { + m_Shader->MainThreadCleanup (); + } + + return true; +} + +ShaderLab::PropertySheet *Shader::MakeProperties () const +{ + return m_Shader->MakeProperties (); +} + +#if UNITY_EDITOR + +UInt32 Shader::CalculateUsedVertexComponents (bool lightmapped) +{ + if (m_Shader == NULL) + return 0; + + UInt32 mask = 0; + + const ShaderLab::IntShader::SubShaders& subshaders = m_Shader->GetSubShaders(); + for (int i=0;i<subshaders.size();i++) + { + int npasses = subshaders[i]->GetTotalPassCount(); + for (int p = 0; p < npasses; ++p) + { + mask |= subshaders[i]->GetPass(p)->CalculateUsedVertexComponents(lightmapped); + } + } + + return mask; +} + +int Shader::GetPropertyCount () const +{ + if (!m_ParsedForm) + return 0; + return m_ParsedForm->m_PropInfo.GetPropertyCount(); +} + +const ShaderLab::ParserProperty* Shader::GetPropertyInfo (int propertyNo) const +{ + if (!m_ParsedForm) + return NULL; + return m_ParsedForm->m_PropInfo.GetPropertyInfo (propertyNo); +} + +bool Shader::HasClip() const +{ + return m_ParsedForm ? m_ParsedForm->HasClip() : false; +} + +char const* Shader::GetCustomEditorName() const +{ + if (!m_Shader) + return NULL; + return m_Shader->GetCustomEditor ().c_str (); +} + +#endif + +PROFILER_INFORMATION(gSetPassProfile, "Shader.SetPass", kProfilerRender); + +const ChannelAssigns* Shader::SetPass( int subshaderIndex, int passNo, UInt32 stateKey, const ShaderLab::PropertySheet* props ) +{ + PROFILER_AUTO(gSetPassProfile, this) + return m_Shader->GetSubShader(subshaderIndex).SetPass (passNo, stateKey, props); +} + +bool Shader::CanPassBeRecorded(int subshaderIndex, int passNo) const +{ + const ShaderLab::Pass* pass = m_Shader->GetSubShader(subshaderIndex).GetPass(passNo); + return pass->CanPassBeRecorded(); +} + +ShaderLab::Pass* Shader::GetShadowCasterPassToUse(int subshaderIndex) +{ + ShaderLab::SubShader& subShader = m_Shader->GetSubShader(subshaderIndex); + if (!subShader.ShadowCasterPassEnabled()) + return NULL; + int ssPass = subShader.GetShadowCasterPassIndex(); + if (ssPass >= 0) + return subShader.GetPass(ssPass); + return m_ShadowCasterPass; +} + +ShaderLab::Pass* Shader::GetShadowCollectorPassToUse(int subshaderIndex) +{ + ShaderLab::SubShader& subShader = m_Shader->GetSubShader(subshaderIndex); + int ssPass = subShader.GetShadowCollectorPassIndex(); + if (ssPass >= 0) + return subShader.GetPass(ssPass); + return m_ShadowCollectorPass; +} + + +int Shader::GetActiveSubShaderIndex () const +{ + return GetShaderLabShader()->GetActiveSubShaderIndex(); +} + +bool Shader::SetScript (const TextAsset::ScriptString& script) +{ + Super::SetScriptDontDirty (script); + + m_ShadowCasterPass = NULL; + m_ShadowCollectorPass = NULL; + + ParseAndPostLoad( script.c_str(), script.size() ); + + return m_Shader != gDefaultShaderLabShader; +} + + +namespace ShaderLab { +extern const char* kPassLightModeTypeNames[kShaderPassTypeCount]; +} + + +static ShaderLab::Pass* FindLightModePass( ShaderLab::IntShader* shader, ShaderPassType type ) +{ + using ShaderLab::TagMap; + typedef ShaderLab::IntShader::SubShaders SubShaders; + + static const int lightModeTagID = ShaderLab::GetShaderTagID("LIGHTMODE"); + + const SubShaders& subshaders = shader->GetSubShaders(); + for( SubShaders::const_iterator it = subshaders.begin(); it != subshaders.end(); ++it ) + { + ShaderLab::SubShader& ss = **it; + for( int i = 0; i < ss.GetValidPassCount (); ++i ) + { + const TagMap& tags = ss.GetPass(i)->GetTags(); + TagMap::const_iterator found = tags.find (lightModeTagID); + if( found != tags.end() && (StrICmp( ShaderLab::GetShaderTagName(found->second), ShaderLab::kPassLightModeTypeNames[type] ) == 0 ) ) + { + return ss.GetPass(i); + } + } + } + + return NULL; +} + +void Shader::PostLoad () +{ + m_Shader->PostLoad(); + + // Find out shadow caster/collector passes + Assert (m_ShadowCasterPass == NULL && m_ShadowCollectorPass == NULL); + m_ShadowCasterPass = FindLightModePass( m_Shader, kPassShadowCaster ); + m_ShadowCollectorPass = FindLightModePass( m_Shader, kPassShadowCollector ); +} + +void Shader::AddMaterialUser( ListNode<Unity::Material>& node ) +{ + m_Users.push_back( node ); +} + +PROFILER_INFORMATION(gShaderParseProfile, "Shader.Parse", kProfilerRender); + +void Shader::Parse (const char *str, size_t strLength) +{ + PROFILER_AUTO(gShaderParseProfile, this); + SET_ALLOC_OWNER(this); + + m_NeedsParsing = false; + if( m_Shader != gDefaultShaderLabShader ) + UNITY_DELETE(m_Shader, kMemShader); + + #if UNITY_EDITOR + UNITY_DELETE( m_ParsedForm, kMemShader); + m_ParsedForm = NULL; + ShaderErrors& errors = m_Errors; + #else + ShaderErrors errors; + #endif + + ShaderLab::ParserShader* parsedForm = NULL; + m_Shader = ParseShader (str, strLength, m_Dependencies, m_ShaderIsBaked, &parsedForm, errors, GetGfxDevice().GetRenderer()); + // In editor, keep the parsed form so we can serialize shaders out specific for a platform. In players, delete parsed form now. + #if UNITY_EDITOR + m_ParsedForm = parsedForm; + #else + UNITY_DELETE( parsedForm, kMemShader); + #endif + + + m_ShadowCasterPass = NULL; + m_ShadowCollectorPass = NULL; + m_ShaderName = g_LastParsedShaderName; + + // For no-fixed function hardware and shaders that ended up having only shadow related passes + // (like VertexLit), insert the pass from default shader. Otherwise the shader is supported, but + // has no passes suitable for any rendering path, hence all objects with it are not rendered at all. + if (!gGraphicsCaps.hasFixedFunction && m_Shader && gDefaultShaderLabShader) + { + ShaderLab::SubShader& ss = m_Shader->GetActiveSubShader(); + if (ss.GetHasOnlyShadowPasses()) { + printf_console("Unsupported: %s\n", m_ShaderName.c_str()); + ss.InsertDefaultPass (gDefaultShaderLabShader->GetActiveSubShader().GetPass(0)); + } + } + + if( !m_Shader ) + { + printf_console("Unsupported: %s\n", m_ShaderName.c_str()); + m_Shader = gDefaultShaderLabShader; + return; + } + + if (m_Shader->HasNoSubShaders()) + { + char buf[256]; + snprintf(buf, 255, "No valid subshaders in '%s'.shader", GetScriptClassName().c_str()); + ErrorStringObject (buf, this); + UNITY_DELETE(m_Shader, kMemShader); + m_Shader = gDefaultShaderLabShader; + } +} + + +void Shader::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + SET_ALLOC_OWNER(this); + Super::AwakeFromLoad (awakeMode); + const ScriptString& s = GetScript(); + ParseAndPostLoad( s.c_str(), s.size() ); +} + +void Shader::AwakeFromLoadThreaded () +{ + Super::AwakeFromLoadThreaded (); + + // In some cases a shader is constructed (on the loading thread, has shader pointing + // to the default one, and m_NeedsParsing flag set to false). Later on + // actual shader is being loaded, but no one resets the m_NeedsParsing flag. + // This seems to happen only in a very rare combination of shaders loaded via fallbacks + // combined with shaders being referenced from scripts only; and even then it + // highly depends on the order the shaders end up being loaded. Couldn't + // make an automated repro case. + if (m_Shader == gDefaultShaderLabShader) + { + m_Shader = NULL; + m_ShadowCasterPass = NULL; + m_ShadowCollectorPass = NULL; + m_NeedsParsing = true; + } +} + +template<class TransferFunc> +void Shader::Transfer (TransferFunc& transfer) +{ + #if UNITY_EDITOR + if (transfer.IsBuildingTargetPlatform(kBuildAnyPlayerData)) + { + UnityStr newSource = ProduceTargetSpecificShader (m_Script, m_ParsedForm, transfer.GetBuildingTarget()); + + Super::Super::Transfer (transfer); + transfer.Transfer (newSource, "m_Script", kHideInEditorMask); + transfer.Transfer (m_PathName, "m_PathName", kHideInEditorMask); + } + else + Super::Transfer (transfer); + #else + Super::Transfer (transfer); + #endif + + transfer.Transfer (m_Dependencies, "m_Dependencies"); + + // Shaders in player data files and asset bundles should get "shader is baked" flag, + // so they get their dependencies directly instead of through script mapper. + // Need to set this flag to true when building the data files, but properly + // reset it back afterwards. + if (transfer.IsWritingGameReleaseData()) + { + bool shaderIsBaked = true; + transfer.Transfer (shaderIsBaked, "m_ShaderIsBaked"); + } + else + { + transfer.Transfer (m_ShaderIsBaked, "m_ShaderIsBaked"); + } + + transfer.Align(); + + #if UNITY_EDITOR + if (!transfer.IsSerializingForGameRelease() && !transfer.IsRemapPPtrTransfer ()) + { + transfer.Transfer (m_Errors.GetErrors(), "errors"); + transfer.Transfer (m_DefaultTextures, "m_DefaultTextures", kHideInEditorMask); + } + #endif + +} + +char const* Shader::GetName () const { + // shader name is the name of the shader, not the filename + return GetScriptClassName().c_str(); +} + + +bool Shader::IsSupported () const +{ + return m_Shader != gDefaultShaderLabShader; +} + +int Shader::GetSubShaderWithTagValue (int tagNameID, int tagValueID) const +{ + using ShaderLab::TagMap; + typedef ShaderLab::IntShader::SubShaders SubShaders; + + const SubShaders& ss = m_Shader->GetSubShaders(); + int idx = 0; + for( SubShaders::const_iterator i = ss.begin(); i != ss.end(); ++i ) + { + const TagMap& tags = (**i).GetTags(); + TagMap::const_iterator j = tags.find(tagNameID); + if (j != tags.end () && j->second == tagValueID) + return idx; + idx++; + } + return -1; +} + +bool Shader::IsDependentOn (PPtr<Shader> shader) const +{ + for (int i = 0; i < m_Dependencies.size (); ++i) + if (m_Dependencies[i] == shader || + m_Dependencies[i]->IsDependentOn (shader)) + return true; + + return false; +} + +Shader* Shader::GetDependency (const std::string& name) +{ + if (!m_Shader) + return NULL; + const std::string* dependencyName = m_Shader->GetDependency (name); + if (!dependencyName || dependencyName->empty()) + return NULL; + Shader* res = FindShaderLabShader (*dependencyName, m_Dependencies, m_ShaderIsBaked); + return res; +} + + +void Shader::UnloadDefaultShaderLabShader() +{ + UNITY_DELETE(gDefaultShaderLabShader, kMemShader); + gDefaultShaderLabShader = NULL; + if (gDefaultShader) + gDefaultShader->m_Shader = NULL; +} + +void Shader::LoadDefaultShaderLabShader() +{ + ShaderLab::ParserShader* parsedForm = NULL; + ShaderErrors errors; + ShaderPtrVector dependencies; + gDefaultShaderLabShader = ParseShader (gDefaultString, strlen(gDefaultString), dependencies, false, &parsedForm, errors, GetGfxDevice().GetRenderer()); + if (errors.HasErrorsOrWarnings()) { + ErrorString ("Default Shader has errors! Something is wrong."); + } + UNITY_DELETE(parsedForm, kMemShader); + + if (gDefaultShader) + { + gDefaultShader->m_Shader = gDefaultShaderLabShader; + gDefaultShader->PostLoad(); + } +} + +static void MakeDefaultShaderLabShader () +{ + if (gDefaultShaderLabShader) + return; + gShaderLabContainer = UNITY_NEW_AS_ROOT(int, kMemShader, "ShaderLab", ""); + SET_ALLOC_OWNER(gShaderLabContainer); + ShaderLab::InitShaderLab (); + + Shader::LoadDefaultShaderLabShader(); +} + +void CleanupShaders () +{ + Shader::UnloadDefaultShaderLabShader(); + ShaderLab::CleanupShaderLab(); + // still some allocations left in shaderlab, so we don't want to delete this one before someone has cleaned up shaderlab +// UNITY_DELETE(gShaderLabContainer, kMemShader); +} + +void Shader::DeleteAllShaders( std::vector<SInt32>& outShaderObjects ) +{ + outShaderObjects.clear(); + Object::FindAllDerivedObjects (ClassID (Shader), &outShaderObjects); + for (std::vector<SInt32>::iterator i = outShaderObjects.begin(); i != outShaderObjects.end(); i++) + { + Shader *s = PPtr<Shader> (*i); + for (MaterialList::iterator sit = s->m_Users.begin(); sit != s->m_Users.end(); ++sit) + { + (*sit)->InvalidateDisplayLists(); + } + if (s->m_Shader != gDefaultShaderLabShader) + { + UNITY_DELETE(s->m_Shader, kMemShader); + s->m_Shader = NULL; + s->m_ShadowCasterPass = NULL; + s->m_ShadowCollectorPass = NULL; + s->m_NeedsParsing = true; + } + } +} + +void Shader::RecreateAllShaders( const std::vector<SInt32>& shaderObjects ) +{ + for( std::vector<SInt32>::const_iterator i = shaderObjects.begin(); i != shaderObjects.end(); ++i ) + { + Shader *s = PPtr<Shader> (*i); + if( s != gDefaultShader ) + { + const ScriptString& script = s->GetScript(); + s->ParseAndPostLoad( script.c_str(), script.size() ); + } + } +} + + +void Shader::ReloadAllShaders() +{ + std::vector<SInt32> derivedObjects; + DeleteAllShaders( derivedObjects ); + RecreateAllShaders( derivedObjects ); +} + +void Shader::SetGLobalMaximumShaderLOD( int lod ) +{ + if( ShaderLab::g_GlobalMaximumShaderLOD == lod ) + return; + ShaderLab::g_GlobalMaximumShaderLOD = lod; + + // notify all shaders of global LOD change + std::vector<SInt32> shaders; + Object::FindAllDerivedObjects (ClassID (Shader), &shaders); + for (std::vector<SInt32>::iterator i = shaders.begin(); i != shaders.end(); ++i) + { + Shader* s = PPtr<Shader> (*i); + s->m_Shader->PostLoad(); + } +} + +int Shader::GetGlobalMaximumShaderLOD() +{ + return ShaderLab::g_GlobalMaximumShaderLOD; +} + +void Shader::SetMaximumShaderLOD( int lod ) +{ + if(GetMaximumShaderLOD() == lod) + return; + + GetShaderLabShader()->SetMaximumShaderLOD(lod); +} + +int Shader::GetMaximumShaderLOD( ) const +{ + return GetShaderLabShader()->GetMaximumShaderLOD(); +} + + +IMPLEMENT_CLASS (Shader) +IMPLEMENT_OBJECT_SERIALIZE (Shader) diff --git a/Runtime/Shaders/Shader.h b/Runtime/Shaders/Shader.h new file mode 100644 index 0000000..a992d1d --- /dev/null +++ b/Runtime/Shaders/Shader.h @@ -0,0 +1,174 @@ +#ifndef SHADER_H +#define SHADER_H + +#include "Runtime/Scripting/TextAsset.h" +#include <string> +#include "Runtime/Math/Color.h" +#include "Runtime/Utilities/LinkedList.h" +#include "External/shaderlab/Library/ShaderLabErrors.h" +#include "Runtime/Graphics/Texture.h" + +using std::string; + +namespace ShaderLab +{ + class IntShader; + class PropertySheet; + struct ParserProperty; + class Pass; + struct ParserShader; +} +namespace Unity { class Material; } +class Shader; +class ChannelAssigns; +typedef std::vector< PPtr<Shader> > ShaderPtrVector; + + +// A Wrapper for a ShaderLab shader. +// This class handles all the Unity-specific interfacing into the shader. +class Shader : public TextAsset { + public: + REGISTER_DERIVED_CLASS (Shader, TextAsset) + DECLARE_OBJECT_SERIALIZE (Shader) + + Shader (MemLabelId label, ObjectCreationMode mode); + // ~Shader (); declared-by-macro + + virtual bool MainThreadCleanup (); + + // Set the next pass to render + const ChannelAssigns* SetPass (int subshaderIndex, int passNo, UInt32 stateKey, const ShaderLab::PropertySheet* props); + + bool CanPassBeRecorded(int subshaderIndex, int passNo) const; + + ShaderLab::Pass* GetShadowCasterPassToUse(int subshaderIndex); + ShaderLab::Pass* GetShadowCollectorPassToUse(int subshaderIndex); + + bool AnySubshaderHasShadowCasterPass() const { return m_ShadowCollectorPass != NULL; } + bool HasShadowCollectorPass() const { return m_ShadowCollectorPass != NULL; } + int GetActiveSubShaderIndex () const; + + // Set the shader string + virtual bool SetScript (const ScriptString& script); + + // Did this shader compile & work, or are we using the default + bool IsSupported () const; + + // Get the default Shader + static Shader* GetDefault (); + static Shader* GetScreenClearShader (); + static void LoadDefaultShaders (); + + static void UnloadDefaultShaderLabShader(); + static void LoadDefaultShaderLabShader(); + void ResetInternalPointersToNull() + { + m_Shader = NULL; + m_ShadowCasterPass = NULL; + m_ShadowCollectorPass = NULL; + } + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + virtual void AwakeFromLoadThreaded (); + + // Make a new property sheet. + ShaderLab::PropertySheet *MakeProperties () const; + + + #if UNITY_EDITOR + int GetPropertyCount () const; + const ShaderLab::ParserProperty *GetPropertyInfo (int propertyNo) const; + #endif + + // from TextAsset, returns shader name + virtual const UnityStr& GetScriptClassName () const { return m_ShaderName; } + // from Object, returns shader name + virtual char const* GetName () const; + // GetName() is overriden, use this to return name from named object + // A bit of a hack, see case 364626 + char const* GetNamedObjectName() const { return m_Name.empty() ? GetName() : m_Name.c_str (); } + + const ShaderLab::IntShader *GetShaderLabShader() const { return m_Shader; } + ShaderLab::IntShader *GetShaderLabShader() { return m_Shader; } + + // reparses and reloads all shaders + static void ReloadAllShaders(); + // those two do the same as ReloadAllShaders. Use if there is something to be done in between. + static void DeleteAllShaders( std::vector<SInt32>& outShaderObjects ); + static void RecreateAllShaders( const std::vector<SInt32>& shaderObjects ); + + static void SetGLobalMaximumShaderLOD( int lod ); + static int GetGlobalMaximumShaderLOD(); + + void SetMaximumShaderLOD( int lod ); + int GetMaximumShaderLOD() const; + + // Get the index of a subshader with a given type tag. + int GetSubShaderWithTagValue (int tagNameID, int tagValueID) const; + + void AddMaterialUser( ListNode<Unity::Material>& node ); + + bool GetNeedsParsing() const { return m_NeedsParsing; } + + void ParseAndPostLoad( const char* str, size_t strLength ) { Parse(str,strLength); PostLoad(); } + + bool IsDependentOn (PPtr<Shader> shader) const; + Shader* GetDependency (const std::string& name); + const ShaderPtrVector& GetDependencies () const { return m_Dependencies; } + + ShaderLab::Pass* GetShadowCasterPass() { return m_ShadowCasterPass; } + ShaderLab::Pass* GetShadowCollectorPass() { return m_ShadowCollectorPass; } + + #if UNITY_EDITOR + const ShaderLab::ParserShader* GetParsedForm() const { return m_ParsedForm; } + const ShaderErrors& GetErrors() const { return m_Errors; } + ShaderErrors& GetErrors() { return m_Errors; } + UInt32 CalculateUsedVertexComponents (bool lightmapped); + + typedef std::map<UnityStr, PPtr<Texture> > DefaultTexturesMap; + const DefaultTexturesMap& GetDefaultTextures () const { return m_DefaultTextures; } + void SetDefaultTextures (const DefaultTexturesMap& textures) { m_DefaultTextures = textures; } + + bool HasClip() const; + char const* GetCustomEditorName() const; + #endif + +private: + // Function to extract renderqueue & channels from a shader. + void PostLoad (); + // Parse a string into the shader + void Parse (const char *str, size_t strLength); + + ShaderLab::IntShader* m_Shader; // the actual shader + ShaderLab::Pass* m_ShadowCasterPass; // shadow caster pass, if any + ShaderLab::Pass* m_ShadowCollectorPass; // shadow collector pass, if any + + // List of Materials that use this shader + typedef List< ListNode<Unity::Material> > MaterialList; + MaterialList m_Users; + + + // Shaders that are not supported get assigned a "default shader". However, we really + // want to remember it's original name (so we can build players with proper ScriptMapper info, + // and display the correct name in shader popups, etc.). + // + // So remember the actual name here, and not in ShaderLab + // shader instance (since single "default" shaderlab instance is shared by all unsupported shaders). + UnityStr m_ShaderName; + + ShaderPtrVector m_Dependencies; // shader pointers used by Dependencies / UsePasses / Fallbacks from this shader + bool m_ShaderIsBaked; + + bool m_NeedsParsing; + + #if UNITY_EDITOR + ShaderErrors m_Errors; + ShaderLab::ParserShader* m_ParsedForm; + + DefaultTexturesMap m_DefaultTextures; + #endif +}; + +void CleanupShaders (); + +#endif diff --git a/Runtime/Shaders/ShaderKeywords.cpp b/Runtime/Shaders/ShaderKeywords.cpp new file mode 100644 index 0000000..c751574 --- /dev/null +++ b/Runtime/Shaders/ShaderKeywords.cpp @@ -0,0 +1,90 @@ +#include "UnityPrefix.h" +#include "ShaderKeywords.h" +#include <map> + +typedef std::map<std::string, ShaderKeyword> KeywordMap; + +static KeywordMap* s_KeywordMap; +ShaderKeywordSet g_ShaderKeywords; + + +void keywords::Initialize() +{ + Assert(!s_KeywordMap); + s_KeywordMap = UNITY_NEW( KeywordMap, kMemShader); + SET_ALLOC_OWNER(NULL); + // precreate common keywords + + // Important: these must go first + Create( "SPOT" ); + Create( "DIRECTIONAL" ); + Create( "DIRECTIONAL_COOKIE" ); + Create( "POINT" ); + Create( "POINT_COOKIE" ); + + Create( "SHADOWS_OFF" ); + Create( "SHADOWS_DEPTH" ); + Create( "SHADOWS_SCREEN" ); + Create( "SHADOWS_CUBE" ); + Create( "SHADOWS_SOFT" ); + Create( "SHADOWS_SPLIT_SPHERES" ); + Create( "SHADOWS_NATIVE" ); + + Create( "LIGHTMAP_OFF" ); + Create( "LIGHTMAP_ON" ); + + Create( "DIRLIGHTMAP_OFF" ); + Create( "DIRLIGHTMAP_ON" ); + + Create( "VERTEXLIGHT_ON" ); + Create( "SOFTPARTICLES_ON" ); + + Create( "HDR_LIGHT_PREPASS_ON" ); +} + +void keywords::Cleanup() +{ + UNITY_DELETE( s_KeywordMap, kMemShader); + s_KeywordMap = NULL; +} + +ShaderKeyword keywords::Create( const std::string& name ) +{ + if( !s_KeywordMap ) { + Initialize(); + ANALYSIS_ASSUME(s_KeywordMap); + } + + KeywordMap::const_iterator it = s_KeywordMap->find( name ); + if( it != s_KeywordMap->end() ) + return it->second; + + ShaderKeyword key = s_KeywordMap->size(); + if( key >= kMaxShaderKeywords ) + { + AssertString( Format("Maximum number (%i) of shader keywords exceeded, keyword %s will be ignored", kMaxShaderKeywords, name.c_str()) ); + } + else + { + SET_ALLOC_OWNER(NULL); + s_KeywordMap->insert( std::make_pair( name, key ) ); + } + return key; +} + +#if UNITY_EDITOR +std::string keywords::GetKeywordName (ShaderKeyword k) +{ + if (!s_KeywordMap) { + Initialize(); + ANALYSIS_ASSUME(s_KeywordMap); + } + for (KeywordMap::const_iterator it = s_KeywordMap->begin(); it != s_KeywordMap->end(); ++it) + { + if (it->second == k) + return it->first; + } + AssertString ("Requesting non existing shader keyword"); + return ""; +} +#endif diff --git a/Runtime/Shaders/ShaderKeywords.h b/Runtime/Shaders/ShaderKeywords.h new file mode 100644 index 0000000..7a273bb --- /dev/null +++ b/Runtime/Shaders/ShaderKeywords.h @@ -0,0 +1,43 @@ +#pragma once + +#include <string> + +typedef int ShaderKeyword; + +// We use about 14 builtin keywords now, 18 more for the user should be fine now +// (shader size explosion will happen before that limit is reached anyways...) +const int kMaxShaderKeywords = 64; +struct ShaderKeywordSet { +public: + explicit ShaderKeywordSet () : mask(0) {} + + void Enable (ShaderKeyword key) { mask |= (0x1ULL<<key); } + void Disable (ShaderKeyword key) { mask &= ~(0x1ULL<<key); } + bool IsEnabled (ShaderKeyword key) const { return (mask & (0x1ULL<<key)) != 0; } + void Reset () { mask = 0; } + + UInt64 GetMask() const { return mask; } + void SetMask (UInt64 m) { mask = m; } + + bool operator== (const ShaderKeywordSet& o) const { return mask==o.mask; } + bool operator!= (const ShaderKeywordSet& o) const { return mask!=o.mask; } + +private: + UInt64 mask; +}; + +extern ShaderKeywordSet g_ShaderKeywords; + + +namespace keywords { + + void Initialize(); + void Cleanup(); + + ShaderKeyword Create( const std::string& name ); + + #if UNITY_EDITOR + std::string GetKeywordName (ShaderKeyword k); + #endif + +} // namespace keywords diff --git a/Runtime/Shaders/ShaderNameRegistry.cpp b/Runtime/Shaders/ShaderNameRegistry.cpp new file mode 100644 index 0000000..58e9e34 --- /dev/null +++ b/Runtime/Shaders/ShaderNameRegistry.cpp @@ -0,0 +1,235 @@ +#include "UnityPrefix.h" +#include "ShaderNameRegistry.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/BaseClasses/ManagerContext.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "External/shaderlab/Library/shaderlab.h" + + +ScriptMapper::ScriptMapper (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_Shaders.SetObjectToDirty (this); +} + +ScriptMapper::~ScriptMapper () +{ +} + + +void ScriptMapper::AddShader (Shader& ptr) +{ + m_Shaders.Add (ptr.GetScriptClassName (), &ptr); +} + +void ScriptMapper::AddBuiltinShader (const string& shaderClassName, PPtr<Shader> ptr) +{ + m_Shaders.Add (shaderClassName, ptr); +} + + +static const char* kBackwardsCompatShaderNames[] = { + " VertexLit", "VertexLit", + " Diffuse", "Diffuse", + " Glossy", "Specular", + " Bumped", "Bumped Diffuse", + " BumpedSpecular", "Bumped Specular", + "ParallaxBump/Diffuse", "Parallax Diffuse", + "ParallaxBump/Specular", "Parallax Specular", + + "Alpha/VertexLit", "Transparent/VertexLit", + "Alpha/Diffuse", "Transparent/Diffuse", + "Alpha/Glossy", "Transparent/Specular", + "Alpha/Bumped", "Transparent/Bumped Diffuse", + "Alpha/BumpedSpecular", "Transparent/Bumped Specular", + "ParallaxBump/AlphaDiffuse", "Transparent/Parallax Diffuse", + "ParallaxBump/AlphaSpecular", "Transparent/Parallax Specular", + + //"Reflective/VertexLit", "Reflective/VertexLit", + //"Reflective/Diffuse", "Reflective/Diffuse", + "Reflective/Glossy", "Reflective/Specular", + "Reflective/Bumped", "Reflective/Bumped Diffuse", + "Reflective/BumpedSpecular", "Reflective/Bumped Specular", + "ParallaxBump/ReflectDiffuse", "Reflective/Parallax Diffuse", + "ParallaxBump/ReflectSpecular", "Reflective/Parallax Specular", + + "Lightmapped/Glossy", "Legacy Shaders/Lightmapped/Specular", + "Lightmapped/Bumped", "Legacy Shaders/Lightmapped/Bumped Diffuse", + "Lightmapped/BumpedSpecular", "Legacy Shaders/Lightmapped/Bumped Specular", + "Lightmapped/VertexLit", "Legacy Shaders/Lightmapped/VertexLit", + "Lightmapped/Diffuse", "Legacy Shaders/Lightmapped/Diffuse", + "Lightmapped/Specular", "Legacy Shaders/Lightmapped/Specular", + "Lightmapped/Bumped Diffuse", "Legacy Shaders/Lightmapped/Bumped Diffuse", + "Lightmapped/Bumped Specular", "Legacy Shaders/Lightmapped/Bumped Specular", + "Diffuse Fast", "Legacy Shaders/Diffuse Fast", + + + //"Self-Illumin/VertexLit", "Self-Illumin/VertexLit", + //"Self-Illumin/Diffuse", "Self-Illumin/Diffuse", + "Self-Illumin/Glossy", "Self-Illumin/Specular", + "Self-Illumin/Bumped", "Self-Illumin/Bumped Diffuse", + "Self-Illumin/BumpedSpecular", "Self-Illumin/Bumped Specular", + "ParallaxBump/IlluminDiffuse", "Self-Illumin/Parallax Diffuse", + "ParallaxBump/IlluminSpecular", "Self-Illumin/Parallax Specular", + + " DiffuseDetail", "Diffuse Detail", + " Diffuse (fast)", "Legacy Shaders/Diffuse Fast", + " Decal", "Decal", + + "Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass", "Nature/Terrain/Diffuse", +}; + + +static const char* GetNonLegacyShaderName(const std::string& name) +{ + const int kShaderNameCount = sizeof(kBackwardsCompatShaderNames) / sizeof(kBackwardsCompatShaderNames[0]) / 2; + for (int i = 0; i < kShaderNameCount; ++i) + { + if (strcmp( kBackwardsCompatShaderNames[i*2+0], name.c_str()) == 0) + return kBackwardsCompatShaderNames[i*2+1]; + } + return NULL; +} + + +Shader *ScriptMapper::FindShader (const string &name) +{ + Shader* result = m_Shaders.Find (name); + if( result != NULL ) + return result; + + // If shader is not found, try old names (before 2.0) of built-in shaders + const char* otherName = GetNonLegacyShaderName(name); + if (otherName) + return m_Shaders.Find(otherName); + + return NULL; +} + + + +template<class TransferFunction> +void ScriptMapper::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + AssertIf (transfer.GetFlags() & kPerformUnloadDependencyTracking); + transfer.Transfer (m_Shaders, "m_Shaders"); +} + +bool ScriptMapper::ShouldIgnoreInGarbageDependencyTracking () +{ + return true; +} +IMPLEMENT_CLASS (ScriptMapper) +IMPLEMENT_OBJECT_SERIALIZE (ScriptMapper) +GET_MANAGER (ScriptMapper) +GET_MANAGER_PTR(ScriptMapper) + + +static void ParseShaderIfNeeded (Shader* shader) +{ + Assert (!shader->GetNeedsParsing() || !shader->GetShaderLabShader()); + if (!shader->GetNeedsParsing()) + return; + + const Shader::ScriptString& script = shader->GetScript(); + // Save current shader name around shader parsing + std::string saveShaderName = g_LastParsedShaderName; + shader->ParseAndPostLoad (script.c_str(), script.size()); + g_LastParsedShaderName = saveShaderName; +} + + +Shader* FindShaderLabShader (const std::string& name, ShaderPtrVector& shaderLookup, bool useLookup) +{ + Assert(GetScriptMapperPtr()); + + // Finding shaders can load new ones. So save and + // restore the currently parsed shader name. + std::string saveShaderName = g_LastParsedShaderName; + + Shader* shader = NULL; + if (useLookup) + { + for (size_t i = 0; i < shaderLookup.size(); ++i) + { + Shader* s = shaderLookup[i]; + if (s == NULL) + continue; + ParseShaderIfNeeded (s); + const std::string& shaderName = s->GetScriptClassName(); + if (shaderName == name) + { + shader = s; + break; + } + // also try non-legacy name of the shader we're searching for, + // in case someone does " Diffuse" but we're meant + // to find "Diffuse" etc. + const char* nonLegacyShaderName = GetNonLegacyShaderName(name.c_str()); + if (nonLegacyShaderName && !strcmp(shaderName.c_str(), nonLegacyShaderName)) + { + shader = s; + break; + } + } + } + else + { + shader = GetScriptMapper().FindShader (name); + } + + g_LastParsedShaderName = saveShaderName; + + + if (shader) + { + ParseShaderIfNeeded (shader); + + if (!useLookup) + { + PPtr<Shader> shaderPtr(shader); + if (std::find(shaderLookup.begin(), shaderLookup.end(), shaderPtr) == shaderLookup.end()) + shaderLookup.push_back (shaderPtr); + } + + return shader; + } + + return NULL; +} + + + +// ------------------------------------------------------------------- + + + +#if ENABLE_UNIT_TESTS + +#include "External/shaderlab/Library/SLParserData.h" + + +static void TestCanFindLegacyShaderNamesWhenUsingLookup () +{ + const char* oldName = "Lightmapped/VertexLit"; // legacy name + ScriptMapper& sm = GetScriptMapper(); + Shader* shader1 = sm.FindShader(oldName); + Assert (shader1 != NULL); + + // now check if FindShaderLabShader can find it when using lookup table + ShaderPtrVector lookup; + lookup.push_back(shader1); + Shader* shader2 = FindShaderLabShader (oldName, lookup, true); + Assert (shader2 == shader1); +} + + +void TestHighLevelShaderNameRegistryTests() +{ + TestCanFindLegacyShaderNamesWhenUsingLookup(); +} + +#endif // #if ENABLE_UNIT_TESTS diff --git a/Runtime/Shaders/ShaderNameRegistry.h b/Runtime/Shaders/ShaderNameRegistry.h new file mode 100644 index 0000000..1643f4d --- /dev/null +++ b/Runtime/Shaders/ShaderNameRegistry.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Runtime/BaseClasses/GameManager.h" +#include <string> +#include <map> +#include "Runtime/Shaders/NameToObjectMap.h" +#include "Runtime/Modules/ExportModules.h" + +using std::string; + +class Shader; +typedef std::vector< PPtr<Shader> > ShaderPtrVector; + + +class EXPORT_COREMODULE ScriptMapper : public GlobalGameManager +{ +public: + typedef std::multimap<UnityStr, PPtr<Shader> > StringToShader; + REGISTER_DERIVED_CLASS (ScriptMapper, GlobalGameManager) + DECLARE_OBJECT_SERIALIZE (ScriptMapper) + + typedef NameToObjectMap<Shader, std::map<PPtr<Shader>, UnityStr>, std::multimap<UnityStr, PPtr<Shader> > > Shaders; + + ScriptMapper (MemLabelId label, ObjectCreationMode mode); + // virtual ~ScriptMapper (); declared-by-macro + + // Add a shader to the system. + void AddShader (Shader& ptr); + void AddBuiltinShader (const std::string& className, PPtr<Shader> ptr); + + // Get a shader by name. + // If the shader could not be loaded (e.g. because it was deleted), it is removed from the master list. + Shader* FindShader (const string &name); + + bool ShouldIgnoreInGarbageDependencyTracking (); + + #if UNITY_EDITOR + + Shader *GetDefaultShader() { return FindShader( "Diffuse" ); } + + const Shaders& GetShaders () { return m_Shaders; } + void SetShaders (const Shaders& shaders) { m_Shaders = shaders; } + + #endif // UNITY_EDITOR + +private: + Shaders m_Shaders; +}; + +ScriptMapper& GetScriptMapper (); +ScriptMapper* GetScriptMapperPtr (); + + +Shader* FindShaderLabShader (const std::string& name, ShaderPtrVector& shaderLookup, bool useLookup); diff --git a/Runtime/Shaders/ShaderSupportScripts.cpp b/Runtime/Shaders/ShaderSupportScripts.cpp new file mode 100644 index 0000000..3f60afa --- /dev/null +++ b/Runtime/Shaders/ShaderSupportScripts.cpp @@ -0,0 +1,19 @@ +#include "UnityPrefix.h" +#include "ShaderSupportScripts.h" +#include "Runtime/Utilities/PathNameUtility.h" + +IMPLEMENT_CLASS (CGProgram) + +CGProgram::CGProgram (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{} + +CGProgram::~CGProgram () +{ +} + +void CGProgram::SetCGProgram( const std::string& str ) +{ + m_CGProgram = str; + m_CGProgramName = GetLastPathNameComponent(m_CGProgram); +} diff --git a/Runtime/Shaders/ShaderSupportScripts.h b/Runtime/Shaders/ShaderSupportScripts.h new file mode 100644 index 0000000..e799721 --- /dev/null +++ b/Runtime/Shaders/ShaderSupportScripts.h @@ -0,0 +1,26 @@ +#ifndef SHADERSUPPORTSCRIPTS_H +#define SHADERSUPPORTSCRIPTS_H + +#include "Runtime/Scripting/TextAsset.h" +#include <string> + + +// Shader include files (mainly exist so that a different icon can be used on them) +class CGProgram : public TextAsset { +public: + REGISTER_DERIVED_CLASS (CGProgram, TextAsset) + + CGProgram (MemLabelId label, ObjectCreationMode mode); + + virtual const UnityStr& GetScriptClassName() const { return m_CGProgramName; } + + void SetCGProgram( const std::string& str ); + const UnityStr& GetCGProgram () const { return m_CGProgram; } + +private: + // Placeholder string used for the original CG program - we only use this at importing + UnityStr m_CGProgram; + UnityStr m_CGProgramName; +}; + +#endif diff --git a/Runtime/Shaders/ShaderTags.h b/Runtime/Shaders/ShaderTags.h new file mode 100644 index 0000000..252b165 --- /dev/null +++ b/Runtime/Shaders/ShaderTags.h @@ -0,0 +1,27 @@ +#ifndef SHADER_TAGS_H +#define SHADER_TAGS_H + +// The pass type of a given pass. +// The shader sets this as a hint to the app as to how it wants to be rendered using the LightMode tag +enum ShaderPassType { + kPassAlways, // Always executed. No lighting. + kPassVertex, // Vertex lights + ambient + kPassVertexLM, // Vertex lighting w/ lightmaps + kPassVertexLMRGBM, // Vertex lighting w/ lightmaps encoded as RGBM + kPassForwardBase, + kPassForwardAdd, + kPassLightPrePassBase, + kPassLightPrePassFinal, + kPassShadowCaster, // Renders object as shadow caster + kPassShadowCollector, // Renders object, collecting shadows into screen space texture + kShaderPassTypeCount // Keep this last! +}; + + +enum ShaderRenderOptions { + kShaderOptionSoftVegetation = 0, // soft vegetation is currently on + kShaderRenderOptionCount // keep this last! +}; + + +#endif diff --git a/Runtime/Shaders/ShaderTests.cpp b/Runtime/Shaders/ShaderTests.cpp new file mode 100644 index 0000000..2139543 --- /dev/null +++ b/Runtime/Shaders/ShaderTests.cpp @@ -0,0 +1,285 @@ +#include "UnityPrefix.h" + +#if ENABLE_UNIT_TESTS +#include "External/UnitTest++/src/UnitTest++.h" + +#include "Runtime/Shaders/Shader.h" +#include "Runtime/Shaders/Material.h" +#include "Runtime/Shaders/ShaderNameRegistry.h" +#include "Runtime/Shaders/GraphicsCaps.h" + +#include "External/shaderlab/Library/intshader.h" +#include "External/shaderlab/Library/properties.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "External/shaderlab/Library/ShaderParser.h" +#include "External/shaderlab/Library/SLParserData.h" +#include "External/shaderlab/Library/ShaderParserInterm.h" +#include "External/shaderlab/Library/ShaderParser.h" + +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Misc/ResourceManager.h" +#include "Runtime/Utilities/Word.h" + +using namespace ShaderLab; + +static void CheckPassAffectingProps_TestHelper (const char* desc, const ShaderLab::Pass* pass, const char* propName, ...) +{ + const PropertyNamesSet& names = pass->GetAffectingProps(); + + vector_set<int> namesFound; + bool error = false; + + // See if properties we expect do in fact affect the pass + const char* currName = propName; + va_list ap; + va_start (ap, propName); + while (currName != NULL) + { + ShaderLab::FastPropertyName currProp = ShaderLab::Property(currName); + if (names.names.find(currProp.index) == names.names.end()) + { + printf_console("ShaderTests '%s': property %s expected to affect it, but it doesn't!\n", desc, currName); + error = true; + } + else + namesFound.insert(currProp.index); + currName = va_arg (ap, const char*); + }; + va_end (ap); + + // Now print all properties that are affecting the pass, but we didn't expect them to + for (vector_set<int>::const_iterator it = names.names.begin(); it != names.names.end(); ++it) + { + if (namesFound.find(*it) == namesFound.end()) + { + ShaderLab::FastPropertyName prop; + prop.index = *it; + printf_console("Shader test %s: property %s is affecting shader, but is not expected to!\n", desc, prop.GetName()); + error = true; + } + } + + // If we had errors, print out all properties that are affecting the pass + if (error) + { + std::string msg = "..shader test failed, properties that are affecting: "; + for (vector_set<int>::const_iterator it = names.names.begin(); it != names.names.end(); ++it) + { + ShaderLab::FastPropertyName name; + name.index = *it; + msg += name.GetName(); + msg += " "; + } + printf_console("%s\n\n", msg.c_str()); + printf_console ("ShaderTests: Shader Pass affecting properties test failed, check output log"); + CHECK (false); + } +} + +struct ParseTestFixture +{ + ShaderErrors errors; + bool ParseTest (const char* text) + { + errors.Clear(); + ShaderLab::ParserShader* parsedForm = PreParseShader (text, strlen(text), kGfxRendererNull, errors); + if (!parsedForm) + return false; + UNITY_DELETE(parsedForm,kMemShader); + return true; + } +}; + +SUITE (ShaderTests) +{ +#if UNITY_EDITOR + + TEST (Shader_ShadowCollectorPassCorrectZWrite_EditorOnly) + { + if (GetGfxDevice().GetRenderer() == kGfxRendererNull) + return; + + // Ensure the presence of the 'VertexLit' shader. + Shader* shader = GetScriptMapper().FindShader ("VertexLit"); + CHECK (shader != NULL); + + // 'VertexLit' shader should always have shadow collector pass. + dynamic_array<ShaderLab::Pass*> collectorPasses; + shader->GetShaderLabShader()->GetActiveSubShader().FindNamedPasses ("SHADOWCOLLECTOR", collectorPasses); + CHECK (!collectorPasses.empty()); + + // shadow collector should use LEqual depth test! + const DeviceDepthState* depthState = collectorPasses[0]->GetState().GetDepthState(); + CHECK (depthState); + CHECK (depthState->sourceState.depthFunc == kFuncLEqual); + + // FindNamedPasses calls Retain(), so do not leak memory: need to release them + for (int i = 0; i < collectorPasses.size(); ++i) + collectorPasses[i]->Release(); + } + + TEST (Shader_RenderQueueRespectsLOD_EditorOnly) + { + // two subshaders with different LOD, using different render queues + const char* kShaderText = + "Shader \"__Foo\" {\n" + "SubShader { LOD 300 Tags {Queue=Geometry} Pass { } }\n" + "SubShader { LOD 100 Tags {Queue=Transparent} Pass { } }\n" + "}\n"; + ShaderLab::ParserShader* parsedForm = NULL; + ShaderErrors errors; + ShaderPtrVector dependencies; + ShaderLab::IntShader* shader = ParseShader(kShaderText, strlen(kShaderText), dependencies, false, &parsedForm, errors, kGfxRendererD3D9); + CHECK (shader && !errors.HasErrorsOrWarnings()); + CHECK (shader->GetSubShaders().size() == 2); + + // uses first subshader, Geometry queue + CHECK (shader->GetRenderQueue() == 2000); + + // set LOD to not use the first subshader + shader->SetMaximumShaderLOD(200); + // should get queue from the 2nd subshader + CHECK (shader->GetRenderQueue() == 3000); + + UNITY_DELETE( parsedForm, kMemShader); + UNITY_DELETE( shader, kMemShader); + } + + TEST (Shader_DependenciesDontHaveDuplicates_EditorOnly) + { + // two subshaders with different LOD, using different render queues + const char* kShaderText = + "Shader \"__Foo\" {\n" + "SubShader {\n" + "UsePass \"VertexLit/SHADOWCASTER\"\n" + "UsePass \"VertexLit/SHADOWCASTER\"\n" + "UsePass \"VertexLit/SHADOWCASTER\"\n" + "}\n" + "Fallback Off\n" + "}\n"; + ShaderLab::ParserShader* parsedForm = NULL; + ShaderErrors errors; + ShaderPtrVector dependencies; + ShaderLab::IntShader* shader = ParseShader(kShaderText, strlen(kShaderText), dependencies, false, &parsedForm, errors, kGfxRendererD3D9); + CHECK (shader && !errors.HasErrorsOrWarnings()); + CHECK (shader->GetSubShaders().size() == 1); + + CHECK (dependencies.size() == 1); + + UNITY_DELETE (parsedForm, kMemShader); + UNITY_DELETE (shader, kMemShader); + } + + static void UsedVertexComponents_TestHelper (const char* name, UInt32 expected, UInt32 expectedLM) + { + Shader* shader = GetScriptMapper().FindShader (name); + Assert (shader); + UInt32 channels = shader->CalculateUsedVertexComponents(false); + Assert (channels == expected); + channels = shader->CalculateUsedVertexComponents(true); + Assert (channels == expectedLM); + } + + TEST (Shader_UsedVertexComponents_EditorOnly) + { + if (gGraphicsCaps.GetEmulation() != GraphicsCaps::kEmulNone || + GetGfxDevice().GetRenderer() == kGfxRendererNull || + GetGfxDevice().GetRenderer() == kGfxRendererD3D11) + return; + + UInt32 expLightingUV = (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0); + UInt32 expLightingUV2 = (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelTexCoord1); + UInt32 expLightingTanUV = (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelTangent); + UInt32 expLightingTanUV2 = (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelTexCoord1) | (1<<kShaderChannelTangent); + UInt32 expUV1 = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0); + UInt32 expColorUV1 = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor); + + UsedVertexComponents_TestHelper ("VertexLit", expLightingUV, expLightingUV2); + UsedVertexComponents_TestHelper ("Diffuse", expLightingUV, expLightingUV2); + UsedVertexComponents_TestHelper ("Bumped Diffuse", expLightingTanUV, expLightingTanUV2); + UsedVertexComponents_TestHelper ("Unlit/Texture", expUV1, expUV1); + UsedVertexComponents_TestHelper ("Particles/Additive", expColorUV1, expColorUV1); + } + + TEST (Shader_PassAffectingPropsWork_EditorOnly) + { + if (GetGfxDevice().GetRenderer() == kGfxRendererNull || gGraphicsCaps.shaderCaps < kShaderLevel2) + return; + + Shader* shSpec = GetBuiltinExtraResource<Shader>("Normal-Glossy.shader"); + CHECK (shSpec != NULL); + + CheckPassAffectingProps_TestHelper("Specular ForwardBase pass", shSpec->GetShaderLabShader()->GetSubShader(0).GetPass(0), "_MainTex", "_Color", "_Shininess", "_SpecColor", "_MainTex_ST", "_ShadowMapTexture", NULL); + CheckPassAffectingProps_TestHelper("Specular ForwardAdd pass", shSpec->GetShaderLabShader()->GetSubShader(0).GetPass(1), "_MainTex", "_Color", "_Shininess", "_SpecColor", "_MainTex_ST", "_LightTexture0", "_LightTextureB0", NULL); + + // None of user-accessible properties are affecting shadow caster pass + CheckPassAffectingProps_TestHelper("Specular ShadowCaster pass", shSpec->GetShadowCasterPass(), NULL); + + // Specular falls back to VertexLit, so subshader #1 is vertex lit + CheckPassAffectingProps_TestHelper("VertexLit vertex pass", shSpec->GetShaderLabShader()->GetSubShader(1).GetPass(0), "_MainTex", "_Color", "_Shininess", "_SpecColor", "_Emission", NULL); + + Shader* shCutoutBSpec = GetBuiltinExtraResource<Shader>("AlphaTest-Bumped.shader"); + CHECK (shCutoutBSpec != NULL); + + CheckPassAffectingProps_TestHelper("Cutout BumpSpec ForwardBase pass", shCutoutBSpec->GetShaderLabShader()->GetSubShader(0).GetPass(0), "_MainTex", "_Cutoff", "_BumpMap", "_Color", "_MainTex_ST", "_BumpMap_ST", "_ShadowMapTexture", NULL); + + // Alpha tested shader: texture, cutoff etc. affect shadow caster pass. Bumpmap doesn't affect it. + CheckPassAffectingProps_TestHelper("Cutout BumpSpec pass", shCutoutBSpec->GetShadowCasterPass(), "_MainTex", "_Cutoff", "_Color", "_MainTex_ST", NULL); + } + + TEST (Shader_MaterialGetDefaultProperties_EditorOnly) + { + // Check that a shader with some default property values actually gets them in the material. + const char* kShader = + "Shader \"\" {\n" + "Properties {\n" + "myColor (\"\", color) = (1,2,3,4)\n" + "myFloat (\"\", float) = 0.5\n" + "myRange (\"\", range(0,10)) = 5.0\n" + "myVector (\"\", vector) = (5,6,7,8)\n" + "}\n" + "SubShader { Pass { } }\n" + "}"; + Material* mat = Material::CreateMaterial(kShader,Object::kHideAndDontSave); + Shader* myShader = mat->GetShader(); + + ShaderLab::FastPropertyName propShine = ShaderLab::Property("_Shininess"); + ShaderLab::FastPropertyName propMyColor = ShaderLab::Property("myColor"); + ShaderLab::FastPropertyName propMyFloat = ShaderLab::Property("myFloat"); + ShaderLab::FastPropertyName propMyRange = ShaderLab::Property("myRange"); + ShaderLab::FastPropertyName propMyVector = ShaderLab::Property("myVector"); + + // Don't compare in linear space; returned value close but slightly different due to float conversions. + if (GetActiveColorSpace() == kGammaColorSpace) + { + CHECK (mat->GetColor(propMyColor).Equals(ColorRGBAf(1,2,3,4))); + } + CHECK (mat->GetFloat(propMyFloat) == 0.5f); + CHECK (mat->GetFloat(propMyRange) == 5.0f); + CHECK (mat->GetColor(propMyVector).Equals(ColorRGBAf(5,6,7,8))); + + // Assign a different shader. + Shader* vlitShader = GetScriptMapper().FindShader("VertexLit"); + CHECK (!mat->HasProperty(propShine)); + mat->SetShader (vlitShader); + + // Now we should get _Shininess, with the default value. + float shininess = mat->GetFloat(propShine); + CHECK (shininess == 0.7f); + + // Now, new shader doesn't have myFloat, so it shouldn't be in the runtime PropertySheet. + CHECK (!mat->HasProperty(propMyFloat)); + // But the value should still be intact in the serialized sheet! + CHECK (mat->GetSavedProperties().m_Floats[propMyFloat] == 0.5f); + + DestroySingleObject (mat); + DestroySingleObject (myShader); + } + +#endif // #if UNITY_EDITOR +} + +#endif // #if ENABLE_UNIT_TESTS diff --git a/Runtime/Shaders/UnityPropertySheet.cpp b/Runtime/Shaders/UnityPropertySheet.cpp new file mode 100644 index 0000000..7dc894d --- /dev/null +++ b/Runtime/Shaders/UnityPropertySheet.cpp @@ -0,0 +1,144 @@ +#include "UnityPrefix.h" +#include "UnityPropertySheet.h" +#include "External/shaderlab/Library/shaderlab.h" +#include "External/shaderlab/Library/properties.h" +#include "External/shaderlab/Library/texenv.h" +#include "External/shaderlab/Library/SLParserData.h" +#include "Runtime/Math/ColorSpaceConversion.h" + +UnityPropertySheet::UnityTexEnv::UnityTexEnv() +{ + m_Scale = Vector2f(1,1); + m_Offset = Vector2f(0,0); + m_Texture = 0; +} + + +#if UNITY_EDITOR +void UnityPropertySheet::CullUnusedProperties (const ShaderLab::ParserShader* source) +{ + if (!source) + return; + + TexEnvMap::iterator curTex = m_TexEnvs.begin(); + TexEnvMap::iterator texEnd = m_TexEnvs.end(); + + while (curTex != texEnd) + { + const char* propName = curTex->first.GetName(); + + bool propFound = false; + for (unsigned i = 0; i < source->m_PropInfo.m_Props.size(); ++i) + { + const ShaderLab::ParserProperty& srcProp = source->m_PropInfo.m_Props[i]; + if(srcProp.m_Type == ShaderLab::ParserProperty::kTexture + && ::strcmp( srcProp.m_Name.c_str(), propName ) == 0 + ) + { + propFound = true; + break; + } + } + + if (propFound) + ++curTex; + else + m_TexEnvs.erase(curTex++); + + } +} +#endif + +void UnityPropertySheet::AssignDefinedPropertiesTo (ShaderLab::PropertySheet &target) +{ + for (ShaderLab::PropertySheet::Floats::iterator i = target.GetFloatsMap().begin(); i != target.GetFloatsMap().end(); i++) + { + FloatMap::iterator j = m_Floats.find(i->first); + if (j != m_Floats.end()) + { + i->second = j->second; + } + } + for (ShaderLab::PropertySheet::Vectors::iterator i = target.GetVectorMap().begin(); i != target.GetVectorMap().end(); i++) + { + ColorMap::iterator j = m_Colors.find(i->first); + if (j != m_Colors.end()) + { + if (target.GetColorTag(i->first)) + target.SetVector (i->first, GammaToActiveColorSpace(j->second).GetPtr()); + else + target.SetVector (i->first, j->second.GetPtr()); + } + } + for (ShaderLab::PropertySheet::TexEnvs::iterator i = target.GetTexEnvsMap().begin(); i != target.GetTexEnvsMap().end(); i++) + { + TexEnvMap::iterator j = m_TexEnvs.find(i->first); + if (j != m_TexEnvs.end()) + { + target.SetTextureWithPlacement( i->first, j->second.m_Texture, j->second.m_Scale, j->second.m_Offset ); + } + } +} + + +bool UnityPropertySheet::AddNewShaderlabProps (const ShaderLab::PropertySheet &source) +{ + bool addedAny = false; + for (ShaderLab::PropertySheet::Floats::const_iterator i = source.GetFloatsMap().begin(); i != source.GetFloatsMap().end(); i++) + { + if (m_Floats.insert( std::make_pair(i->first, i->second) ).second) + addedAny = true; + } + for (ShaderLab::PropertySheet::Vectors::const_iterator i = source.GetVectorMap().begin(); i != source.GetVectorMap().end(); i++) + { + // skip texture _ST & _TexelSize properties + bool isTextureProp = false; + for (ShaderLab::PropertySheet::TexEnvs::const_iterator j = source.GetTexEnvsMap().begin(); j != source.GetTexEnvsMap().end(); ++j) + { + if (j->second.scaleOffsetValue == &i->second || j->second.texelSizeValue == &i->second) + { + isTextureProp = true; + break; + } + } + if (isTextureProp) + continue; + + if (m_Colors.insert( std::make_pair(i->first, ColorRGBAf(i->second.x, i->second.y, i->second.z, i->second.w)) ).second) + addedAny = true; + } + for (ShaderLab::PropertySheet::TexEnvs::const_iterator i = source.GetTexEnvsMap().begin(); i != source.GetTexEnvsMap().end(); i++) + { + if (m_TexEnvs.find (i->first) == m_TexEnvs.end()) + { + UnityTexEnv ut; + const Matrix4x4f& mat = i->second.texEnv->GetMatrix(); + ut.m_Scale.Set( mat.Get(0,0), mat.Get(1,1) ); + Vector3f pos = mat.GetPosition(); + ut.m_Offset = Vector2f (pos.x, pos.y); + m_TexEnvs [i->first] = ut; + addedAny = true; + } + } + + return addedAny; +} + +void UnityPropertySheet::AddNewSerializedProps (const UnityPropertySheet &source) +{ + for (UnityPropertySheet::FloatMap::const_iterator i = source.m_Floats.begin(); i != source.m_Floats.end(); i++) + { + if (m_Floats.find (i->first) == m_Floats.end()) + m_Floats [i->first] = i->second; + } + for (UnityPropertySheet::ColorMap::const_iterator i = source.m_Colors.begin(); i != source.m_Colors.end(); i++) + { + if (m_Colors.find (i->first) == m_Colors.end()) + m_Colors [i->first] = i->second; + } + for (UnityPropertySheet::TexEnvMap::const_iterator i = source.m_TexEnvs.begin(); i != source.m_TexEnvs.end(); i++) + { + if (m_TexEnvs.find (i->first) == m_TexEnvs.end()) + m_TexEnvs [i->first] = i->second; + } +} diff --git a/Runtime/Shaders/UnityPropertySheet.h b/Runtime/Shaders/UnityPropertySheet.h new file mode 100644 index 0000000..88d7b0d --- /dev/null +++ b/Runtime/Shaders/UnityPropertySheet.h @@ -0,0 +1,67 @@ +#pragma once + +#include "External/shaderlab/Library/FastPropertyName.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Graphics/Texture.h" + +namespace ShaderLab { + class PropertySheet; + struct ParserShader; +} + +// Serialized material property data (colors, textures, ...). +// This is used only for saving & loading of materials; at runtime ShaderLab PropertySheets +// are used (they can have more data that's not serialized, like matrices etc.). +class UnityPropertySheet +{ +public: + DECLARE_SERIALIZE (UnityPropertySheet) + struct UnityTexEnv { + UnityTexEnv(); + + DECLARE_SERIALIZE (UnityTexEnv) + Vector2f m_Scale; + Vector2f m_Offset; + PPtr<Texture> m_Texture; + }; + typedef std::map<ShaderLab::FastPropertyName, UnityTexEnv> TexEnvMap; + typedef std::map<ShaderLab::FastPropertyName, float> FloatMap; + typedef std::map<ShaderLab::FastPropertyName, ColorRGBAf> ColorMap; + TexEnvMap m_TexEnvs; + FloatMap m_Floats; + ColorMap m_Colors; + + // Set the properties of target. + // This attempts to fill the properties of target with any info this may have. + // It never adds a property to target. + void AssignDefinedPropertiesTo (ShaderLab::PropertySheet &target); + + // Add any properties defined by source, without overwriting anything already here. + bool AddNewShaderlabProps (const ShaderLab::PropertySheet &source); + void AddNewSerializedProps (const UnityPropertySheet &source); + +#if UNITY_EDITOR + // Remove any properties not used by source. + // This is called when building a player by Material::Transfer + void CullUnusedProperties (const ShaderLab::ParserShader* source); +#endif +}; + +template<class TransferFunc> +void UnityPropertySheet::Transfer (TransferFunc& transfer) +{ + transfer.SetVersion (2); + + TRANSFER (m_TexEnvs); + TRANSFER (m_Floats); + TRANSFER (m_Colors); +} + +template<class TransferFunc> +void UnityPropertySheet::UnityTexEnv::Transfer (TransferFunc& transfer) +{ + TRANSFER (m_Texture); + TRANSFER (m_Scale); + TRANSFER (m_Offset); +} diff --git a/Runtime/Shaders/VBO.cpp b/Runtime/Shaders/VBO.cpp new file mode 100644 index 0000000..700be8a --- /dev/null +++ b/Runtime/Shaders/VBO.cpp @@ -0,0 +1,170 @@ +#include "UnityPrefix.h" +#include "VBO.h" +#include "GraphicsCaps.h" + +#include <cstring> +#include "Runtime/Utilities/OptimizationUtility.h" +#include "Runtime/Math/Color.h" +#include "Runtime/GfxDevice/GfxDevice.h" + +#if UNITY_XENON +#include <ppcintrinsics.h> +#endif + + + +static const int kChannelVertexSize[kShaderChannelCount] = { + 3 * sizeof (float), // position + 3 * sizeof (float), // normal + 1 * sizeof (UInt32),// color + 2 * sizeof (float), // uv0 + 2 * sizeof (float), // uv1 + 4 * sizeof (float), // tangent +}; + +static const UInt8 kDefaultVertexChannelDimensionCount [kShaderChannelCount] = { + 3, 3, 1, 2, 2, 4 +}; + +// ----------------------------------------------------------------------------- +// VBO common + +int VBO::GetDefaultChannelByteSize (int channelNum) +{ + DebugAssertIf( channelNum < 0 || channelNum >= kShaderChannelCount ); + return kChannelVertexSize[channelNum]; +} + +int VBO::GetDefaultChannelFormat(int channelNum) +{ + DebugAssertIf( channelNum < 0 || channelNum >= kShaderChannelCount ); + return (kShaderChannelColor == channelNum) ? kChannelFormatColor : kChannelFormatFloat; +} + +int VBO::GetDefaultChannelDimension(int channelNum) +{ + return kDefaultVertexChannelDimensionCount [channelNum]; +} + +bool VBO::IsAnyStreamMapped() const +{ + for (int i = 0; i < kMaxVertexStreams; ++i) + if (m_IsStreamMapped[i]) + return true; + + return false; +} + +bool VBO::HasStreamWithMode(StreamMode mode) const +{ + for (int i = 0; i < kMaxVertexStreams; ++i) + if (m_StreamModes[i] == mode && m_Streams[i].channelMask) + return true; + + return false; +} + +size_t GetVertexSize (unsigned shaderChannelsMask) +{ + size_t size = 0; + for (int i=0; i<kShaderChannelCount; ++i, shaderChannelsMask >>= 1) + if (shaderChannelsMask & 1) + size += kChannelVertexSize[i]; + return size; +} + +DynamicVBO::DynamicVBO() +: m_LastChunkShaderChannelMask(0) +, m_LastChunkStride(0) +, m_LastChunkVertices(0) +, m_LastChunkIndices(0) +, m_LastRenderMode(kDrawIndexedTriangles) +, m_LendedChunk(false) +{ +} + + +// ---------------------------------------------------------------------- + + +void CopyVertexStream( const VertexBufferData& sourceData, void* buffer, unsigned stream ) +{ + DebugAssert(stream < kMaxVertexStreams); + + if (!sourceData.buffer) + return; + + const StreamInfo& info = sourceData.streams[stream]; + const UInt8* src = static_cast<const UInt8*>(sourceData.buffer) + info.offset; + size_t size = CalculateVertexStreamSize(sourceData, stream); + #if UNITY_XENON + XMemCpyStreaming_WriteCombined(buffer, src, size); + #else + memcpy(buffer, src, size); + #endif +} + +void CopyVertexBuffer( const VertexBufferData& sourceData, void* buffer ) +{ + if (sourceData.buffer) + memcpy(buffer, sourceData.buffer, sourceData.bufferSize); +} + +void GetVertexStreamOffsets( const VertexBufferData& sourceData, size_t dest[kShaderChannelCount], size_t baseOffset, unsigned stream ) +{ + for (int i = 0; i < kShaderChannelCount; ++i) + { + const ChannelInfo& channel = sourceData.channels[i]; + dest[i] = channel.IsValid() ? baseOffset + channel.CalcOffset(sourceData.streams) : 0; + } +} + +void GetVertexStreamPointers( const VertexBufferData& sourceData, void* dest[kShaderChannelCount], void* basePtr, unsigned stream ) +{ + for (int i = 0; i < kShaderChannelCount; ++i) + { + const ChannelInfo& channel = sourceData.channels[i]; + dest[i] = channel.IsValid() ? (UInt8*)basePtr + channel.CalcOffset(sourceData.streams) : NULL; + } +} + + +void FillIndexBufferForQuads (UInt16* dst, int dstSize, const UInt16* src, int quadCount) +{ + int canFitQuads = dstSize / kVBOIndexSize / 6; + quadCount = std::min(quadCount, canFitQuads); + + for (int i = 0; i < quadCount; ++i) + { + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + *dst++ = src[0]; + *dst++ = src[2]; + *dst++ = src[3]; + src += 4; + } +} + + + +// -------------------------------------------------------------------------- + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (VBOTests) +{ + + TEST(FillIndexBufferForQuadsDoesNotOverwritePastEnd) + { + UInt16 srcBuffer[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + UInt16 dstBuffer[] = { 17, 13, 17, 13, 17, 13, 1337 }; + FillIndexBufferForQuads (dstBuffer, sizeof(dstBuffer)-2, srcBuffer, 2); + CHECK_EQUAL (1337, dstBuffer[6]); + } + +} // SUITE + +#endif // ENABLE_UNIT_TESTS diff --git a/Runtime/Shaders/VBO.h b/Runtime/Shaders/VBO.h new file mode 100644 index 0000000..d8931ab --- /dev/null +++ b/Runtime/Shaders/VBO.h @@ -0,0 +1,308 @@ +#ifndef VBO_H +#define VBO_H + +#include "Runtime/GfxDevice/GfxDeviceTypes.h" +#include "Configuration/UnityConfigure.h" +#include "Runtime/Serialize/SerializeUtility.h" +#include "Runtime/Filters/Mesh/VertexData.h" +#include "Runtime/Utilities/LinkedList.h" +#include "Runtime/Modules/ExportModules.h" + +class ChannelAssigns; +class Matrix4x4f; + + +struct MeshPartitionInfo +{ + UInt32 submeshStart; + UInt32 partitionCount; + MeshPartitionInfo() {submeshStart=partitionCount=0;} + + DECLARE_SERIALIZE_NO_PPTR (MeshPartitionInfo); +}; + +struct MeshPartition +{ + int vertexCount; + int vertexOffset; + int indexCount; + int indexByteOffset; + MeshPartition() { vertexCount = vertexOffset = indexCount = indexByteOffset = 0; } + + DECLARE_SERIALIZE_NO_PPTR (MeshPartition); +}; + +struct VertexBufferData +{ + ChannelInfoArray channels; + StreamInfoArray streams; + UInt8* buffer; + int bufferSize; + int vertexCount; + + VertexBufferData() + { + // Channels and streams have default constructors + buffer = 0; + bufferSize = 0; + vertexCount = 0; +#if UNITY_PS3 + numBones = 0; + numInfluences = 0; + inflPerVertex = 0; + bones = NULL; + influences = NULL; +#endif + } + +#if UNITY_PS3 + UInt32 numBones; + UInt32 numInfluences; + UInt32 inflPerVertex; + Matrix4x4f* bones; + void* influences; + + UNITY_VECTOR(kMemVertexData, MeshPartitionInfo) partInfo; + UNITY_VECTOR(kMemVertexData, MeshPartition) partitions; +#endif +}; + +struct IndexBufferData +{ + void* indices; + int count; + UInt32 hasTopologies; // bitmask +}; + +struct VertexStreamData +{ + UInt8* buffer; + UInt32 channelMask; + int stride; + int vertexCount; +}; + +size_t GetChannelFormatSize (UInt8 format); + +class EXPORT_COREMODULE VBO : public ListElement +{ +public: + virtual ~VBO() { } + + virtual void UpdateVertexData( const VertexBufferData& buffer ) = 0; + virtual void UpdateIndexData (const IndexBufferData& buffer) = 0; + virtual void DrawVBO (const ChannelAssigns& channels, UInt32 firstIndexByte, UInt32 indexCount, + GfxPrimitiveType topology, UInt32 firstVertex, UInt32 vertexCount ) = 0; + #if GFX_ENABLE_DRAW_CALL_BATCHING + virtual void DrawCustomIndexed( const ChannelAssigns& channels, void* indices, UInt32 indexCount, + GfxPrimitiveType topology, UInt32 vertexRangeBegin, UInt32 vertexRangeEnd, UInt32 drawVertexCount ) = 0; + #endif + + // recreate hardware buffers + virtual void Recreate() { } + + enum { kMaxQuads = 65536/4 - 4 }; // so we fit into 16 bit indices, minus some more just in case + + // For writing directly to VBO. VBO must be filled (UpdateData) + // at least once; and vertex layout + topology from the last fill + // is used. For example, for skinned meshes you have to call + // UpdateData on start and each time layout/topology changes; + // then map,write,unmap for each skinning. + // + // In some situations a vertex buffer might become lost; then you need to do UpdateData + // again before using Map. + virtual bool MapVertexStream( VertexStreamData& outData, unsigned stream ) = 0; + virtual void UnmapVertexStream( unsigned stream ) = 0; + virtual bool IsVertexStreamMapped( unsigned stream ) const { return m_IsStreamMapped[stream]; } + + virtual bool IsVertexBufferLost() const = 0; + virtual bool IsIndexBufferLost() const { return false; } + + virtual bool IsUsingSourceVertices() const { return false; } + virtual bool IsUsingSourceIndices() const { return false; } + + // WARNING: no checks will be done. So use wuth caution. + // Actually you should only use it coupled with Mesh unloading its VertexData + virtual void UnloadSourceVertices() {} + + // Tell vertices will be mapped from render thread + virtual void SetMappedFromRenderThread( bool ) {} + + // Whats the access pattern for modifying vertices? + enum StreamMode + { + kStreamModeNoAccess, + kStreamModeWritePersist, + kStreamModeDynamic, + kStreamModeCount + }; + + virtual void SetVertexStreamMode( unsigned stream, StreamMode mode ) { m_StreamModes[stream] = mode; } + StreamMode GetVertexStreamMode( unsigned stream ) const { return StreamMode(m_StreamModes[stream]); } + int GetStreamStride( unsigned stream = 0) const { return m_Streams[stream].stride; } + + // TODO: probably unify with vertex streams mode, or extract ibo data altogether in different class + virtual void SetIndicesDynamic(bool dynamic) { m_IndicesDynamic = dynamic; } + bool AreIndicesDynamic() const { return m_IndicesDynamic; } + + static int GetDefaultChannelByteSize (int channelNum); + static int GetDefaultChannelFormat(int channelNum); + static int GetDefaultChannelDimension(int channelNum); + + virtual int GetRuntimeMemorySize() const = 0; + + bool GetHideFromRuntimeStats() const { return m_HideFromRuntimeStats; } + void SetHideFromRuntimeStats( bool flag ) { m_HideFromRuntimeStats = flag; } + +#if GFX_SUPPORTS_D3D9 + virtual void ResetDynamicVB() {} +#endif + + // TODO: that is actually how it would/should work in the feature + // or at least what i understood speaking with kaspar + // for now lets limit to gles2 where it is really needed +#if GFX_SUPPORTS_OPENGLES20 + virtual void MarkBuffersLost() {}; +#endif + + virtual void UseAsStreamOutput() { } + +#if UNITY_XENON + virtual void AddExtraUvChannels( const UInt8* data, UInt32 size, int extraUvCount ) = 0; + virtual void CopyExtraUvChannels( VBO* source ) = 0; +#endif + +protected: + VBO() + { + for (int i=0; i<kMaxVertexStreams; i++) + { + m_Streams[i].Reset(); + m_StreamModes[i] = kStreamModeNoAccess; + m_IsStreamMapped[i] = false; + } + m_HideFromRuntimeStats = false; + m_IndicesDynamic = false; + } + + bool IsAnyStreamMapped() const; + bool HasStreamWithMode(StreamMode mode) const; + + StreamInfoArray m_Streams; + UInt8 m_StreamModes[kMaxVertexStreams]; + bool m_IsStreamMapped[kMaxVertexStreams]; + bool m_HideFromRuntimeStats; + bool m_IndicesDynamic; +}; + + +// Usage: +// 1) GetChunk +// if this returns false, don't use data pointers, bail out +// 2) fill with data +// 3) ReleaseChunk +// 4) DrawChunk (possibly multiple times) +// +// The key is that drawing must happen immediately after filling the chunk, because the next +// GetChunk might destroy the previous chunk's data. So never count on chunks being persistent. +class DynamicVBO { +public: + virtual ~DynamicVBO() { } + + enum RenderMode { + kDrawIndexedTriangles, // arbitrary triangle list + kDrawTriangleStrip, // no index buffer, one strip + kDrawQuads, // no index buffer, four vertices per quad + kDrawIndexedTriangleStrip, // arbitrary triangle strip + kDrawIndexedLines, // arbitraty line list + kDrawIndexedPoints, // arbitraty point lits + kDrawIndexedQuads, // arbitraty quad lits +#if UNITY_FLASH + // ONLY IMPLEMENTED there for better immediate performance + kDrawTriangles, // no index buffer triangle list +#endif + }; + + // Gets a chunk of vertex/index buffer to write into. + // + // maxVertices/maxIndices is the capacity of the returned chunk; you have to pass actually used + // amounts in ReleaseChunk afterwards. + // + // maxIndices and outIB are only used for kDrawIndexedTriangles render mode. + // For other ones they must be 0/NULL. + // + // Returns false if can't obtain a chunk for whatever reason. + virtual bool GetChunk( UInt32 shaderChannelMask, UInt32 maxVertices, UInt32 maxIndices, RenderMode renderMode, void** outVB, void** outIB ) = 0; + + virtual void ReleaseChunk( UInt32 actualVertices, UInt32 actualIndices ) = 0; + + virtual void DrawChunk (const ChannelAssigns& channels) = 0; + + // dynamic vbo might be backed by real vbo that needs to be recreated + virtual void Recreate() {} + + static bool IsIndexed(RenderMode mode) { return mode == kDrawIndexedTriangles || mode == kDrawIndexedTriangleStrip; } + +protected: + DynamicVBO(); + +protected: + UInt32 m_LastChunkShaderChannelMask; + UInt32 m_LastChunkStride, m_LastChunkVertices, m_LastChunkIndices; + RenderMode m_LastRenderMode; + bool m_LendedChunk; +}; + + +void CopyVertexStream( const VertexBufferData& sourceData, void* buffer, unsigned stream ); +void CopyVertexBuffer( const VertexBufferData& sourceData, void* buffer ); + +void FillIndexBufferForQuads (UInt16* dst, int dstSize, const UInt16* src, int quadCount); + +void GetVertexStreamOffsets( const VertexBufferData& sourceData, size_t dest[kShaderChannelCount], size_t baseOffset, unsigned stream ); +void GetVertexStreamPointers( const VertexBufferData& sourceData, void* dest[kShaderChannelCount], void* basePtr, unsigned stream ); + +static inline int GetVertexChannelSize( const VertexBufferData& buffer, int i ) +{ + return VBO::GetDefaultChannelByteSize(i) * buffer.vertexCount; +} + +int CalculateOffset( UInt32 channelMask ); + +static inline int CalculateVertexStreamSize (const StreamInfo& stream, int vertexCount) +{ + return stream.stride * vertexCount; +} + +static inline int CalculateVertexStreamSize (const VertexBufferData& buffer, unsigned stream) +{ + Assert(stream < kMaxVertexStreams); + return CalculateVertexStreamSize(buffer.streams[stream], buffer.vertexCount); +} + +const int kVBOIndexSize = sizeof(UInt16); +inline int CalculateIndexBufferSize (const IndexBufferData& buffer) +{ + int size = 0; + if (buffer.indices) + size += buffer.count * kVBOIndexSize; + return size; +} + +static inline int GetPrimitiveCount (int indexCount, GfxPrimitiveType topology, bool nativeQuads) +{ + switch (topology) { + case kPrimitiveTriangles: return indexCount / 3; + case kPrimitiveTriangleStripDeprecated: return indexCount - 2; + case kPrimitiveQuads: return nativeQuads ? indexCount/4 : indexCount/4*2; + case kPrimitiveLines: return indexCount / 2; + case kPrimitiveLineStrip: return indexCount - 1; + case kPrimitivePoints: return indexCount; + default: Assert ("unknown primitive type"); return 0; + }; +} + +// Return vertex size in bytes, with components present as specified by the argument +size_t GetVertexSize (unsigned shaderChannelsMask); + +#endif |