/
SpriteStudioRenderFeature.cs
232 lines (196 loc) · 10.6 KB
/
SpriteStudioRenderFeature.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright (c) Xenko contributors (https://xenko.com) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using Xenko.Core;
using Xenko.Core.Mathematics;
using Xenko.Engine;
using Xenko.Graphics;
using Xenko.Rendering;
namespace Xenko.SpriteStudio.Runtime
{
//TODO this whole renderer is not optimized at all! batching is wrong and depth calculation should be done differently
public class SpriteStudioRenderFeature : RootRenderFeature
{
private EffectInstance selectedSpriteEffect;
private EffectInstance pickingSpriteEffect;
private Sprite3DBatch sprite3DBatch;
public BlendStateDescription MultBlendState;
public BlendStateDescription SubBlendState;
protected override void InitializeCore()
{
base.InitializeCore();
sprite3DBatch = new Sprite3DBatch(Context.GraphicsDevice);
var blendDesc = new BlendStateDescription(Blend.SourceAlpha, Blend.One)
{
RenderTarget0 =
{
BlendEnable = true,
ColorBlendFunction = BlendFunction.ReverseSubtract,
AlphaBlendFunction = BlendFunction.ReverseSubtract
}
};
SubBlendState = blendDesc;
blendDesc = new BlendStateDescription(Blend.DestinationColor, Blend.InverseSourceAlpha)
{
RenderTarget0 =
{
BlendEnable = true,
ColorBlendFunction = BlendFunction.Add,
AlphaSourceBlend = Blend.Zero,
AlphaBlendFunction = BlendFunction.Add
}
};
MultBlendState = blendDesc;
}
protected override void Destroy()
{
sprite3DBatch?.Dispose();
sprite3DBatch = null;
base.Destroy();
}
public override void Prepare(RenderDrawContext context)
{
base.Prepare(context);
// Register resources usage
foreach (var renderObject in RenderObjects)
{
var renderSpriteStudio = (RenderSpriteStudio)renderObject;
if (!renderObject.Enabled)
continue;
var sprites = renderSpriteStudio.Sheet.Sprites;
if (sprites == null)
continue;
foreach (var sprite in sprites)
Context.StreamingManager?.StreamResources(sprite.Texture);
}
}
public override void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex)
{
base.Draw(context, renderView, renderViewStage, startIndex, endIndex);
BlendStateDescription? previousBlendState = null;
DepthStencilStateDescription? previousDepthStencilState = null;
EffectInstance previousEffect = null;
//TODO string comparison ...?
var isPicking = RenderSystem.RenderStages[renderViewStage.Index].Name == "Picking";
var hasBegin = false;
for (var index = startIndex; index < endIndex; index++)
{
var renderNodeReference = renderViewStage.SortedRenderNodes[index].RenderNode;
var renderNode = GetRenderNode(renderNodeReference);
var spriteState = (RenderSpriteStudio)renderNode.RenderObject;
var depthStencilState = DepthStencilStates.DepthRead;
foreach (var node in spriteState.SortedNodes)
{
if (node.Sprite?.Texture == null || node.Sprite.Region.Width <= 0 || node.Sprite.Region.Height <= 0f || node.Hide != 0) continue;
// Update the sprite batch
BlendStateDescription spriteBlending;
switch (node.BaseNode.AlphaBlending)
{
case SpriteStudioBlending.Mix:
spriteBlending = BlendStates.AlphaBlend;
break;
case SpriteStudioBlending.Multiplication:
spriteBlending = MultBlendState;
break;
case SpriteStudioBlending.Addition:
spriteBlending = BlendStates.Additive;
break;
case SpriteStudioBlending.Subtraction:
spriteBlending = SubBlendState;
break;
default:
throw new ArgumentOutOfRangeException();
}
// TODO: this should probably be moved to Prepare()
// Project the position
// TODO: This could be done in a SIMD batch, but we need to figure-out how to plugin in with RenderMesh object
var worldPosition = new Vector4(spriteState.WorldMatrix.TranslationVector, 1.0f);
Vector4 projectedPosition;
Vector4.Transform(ref worldPosition, ref renderView.ViewProjection, out projectedPosition);
var projectedZ = projectedPosition.Z / projectedPosition.W;
var blendState = isPicking ? BlendStates.Default : spriteBlending;
// TODO: the current impementation to determine if the sprite is selected does not work. This should be fixed later at some point
//var currentEffect = isPicking ? GetOrCreatePickingSpriteEffect() : ShadowObject.IsObjectSelected(spriteState.SpriteStudioComponent) ? GetOrCreateSelectedSpriteEffect() : null;
var currentEffect = isPicking ? GetOrCreatePickingSpriteEffect() : null;
// TODO remove this code when material are available
if (previousEffect != currentEffect || blendState != previousBlendState || depthStencilState != previousDepthStencilState)
{
if (hasBegin)
{
sprite3DBatch.End();
}
sprite3DBatch.Begin(context.GraphicsContext, renderView.ViewProjection, SpriteSortMode.Deferred, blendState, null, depthStencilState, RasterizerStates.CullNone, currentEffect);
hasBegin = true;
}
previousEffect = currentEffect;
previousBlendState = blendState;
previousDepthStencilState = depthStencilState;
var sourceRegion = node.Sprite.Region;
var texture = node.Sprite.Texture;
// skip the sprite if no texture is set.
if (texture == null)
continue;
var color4 = Color4.White;
if (isPicking)
{
// TODO move this code corresponding to picking out of the runtime code.
color4 = new Color4(RuntimeIdHelper.ToRuntimeId(spriteState.Source));
}
else
{
if (node.BlendFactor > 0.0f)
{
switch (node.BlendType) //todo this should be done in a shader
{
case SpriteStudioBlending.Mix:
color4 = Color4.Lerp(color4, node.BlendColor, node.BlendFactor) * node.FinalTransparency;
break;
case SpriteStudioBlending.Multiplication:
color4 = Color4.Lerp(color4, node.BlendColor, node.BlendFactor) * node.FinalTransparency;
break;
case SpriteStudioBlending.Addition:
color4 = Color4.Lerp(color4, node.BlendColor, node.BlendFactor) * node.FinalTransparency;
break;
case SpriteStudioBlending.Subtraction:
color4 = Color4.Lerp(color4, node.BlendColor, node.BlendFactor) * node.FinalTransparency;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
else
{
color4 *= node.FinalTransparency;
}
}
Matrix.Multiply(ref node.ModelTransform, ref spriteState.WorldMatrix, out var worldMatrix);
// calculate normalized position of the center of the sprite (takes into account the possible rotation of the image)
var normalizedCenter = new Vector2(node.Sprite.Center.X/sourceRegion.Width - 0.5f, 0.5f - node.Sprite.Center.Y/sourceRegion.Height);
if (node.Sprite.Orientation == ImageOrientation.Rotated90)
{
var oldCenterX = normalizedCenter.X;
normalizedCenter.X = -normalizedCenter.Y;
normalizedCenter.Y = oldCenterX;
}
// apply the offset due to the center of the sprite
var size = node.Sprite.Size;
var centerOffset = Vector2.Modulate(normalizedCenter, size);
worldMatrix.M41 -= centerOffset.X*worldMatrix.M11 + centerOffset.Y*worldMatrix.M21;
worldMatrix.M42 -= centerOffset.X*worldMatrix.M12 + centerOffset.Y*worldMatrix.M22;
// draw the sprite
sprite3DBatch.Draw(texture, ref worldMatrix, ref sourceRegion, ref size, ref color4, node.Sprite.Orientation, SwizzleMode.None, projectedZ);
}
}
if (hasBegin) sprite3DBatch.End();
}
private EffectInstance GetOrCreateSelectedSpriteEffect()
{
return selectedSpriteEffect ?? (selectedSpriteEffect = new EffectInstance(RenderSystem.EffectSystem.LoadEffect("SelectedSprite").WaitForResult()));
}
private EffectInstance GetOrCreatePickingSpriteEffect()
{
return pickingSpriteEffect ?? (pickingSpriteEffect = new EffectInstance(RenderSystem.EffectSystem.LoadEffect("SpritePicking").WaitForResult()));
}
public override Type SupportedRenderObjectType => typeof(RenderSpriteStudio);
}
}