119 Commits

Author SHA1 Message Date
74dcea373c Allow reimporting model prefabs from Content window context menu
Some checks failed
Build Android / Game (Android, Release ARM64) (push) Has been cancelled
Build iOS / Game (iOS, Release ARM64) (push) Has been cancelled
Build Linux / Editor (Linux, Development x64) (push) Has been cancelled
Build Linux / Game (Linux, Release x64) (push) Has been cancelled
Build macOS / Editor (Mac, Development ARM64) (push) Has been cancelled
Build macOS / Game (Mac, Release ARM64) (push) Has been cancelled
Build Windows / Editor (Windows, Development x64) (push) Has been cancelled
Build Windows / Game (Windows, Release x64) (push) Has been cancelled
Tests / Tests (Linux) (push) Has been cancelled
Tests / Tests (Windows) (push) Has been cancelled
2024-06-01 21:43:42 +03:00
70f8492c01 Fix missing vertices in some imported models
The Spatial Sort version was being too greedy when merging vertices
2024-06-01 21:43:42 +03:00
63acdccd43 Fix typos 2024-06-01 21:43:41 +03:00
03f74b0c50 Implement triangulation for non-convex polygons 2024-06-01 21:43:41 +03:00
0d21a3ba1b Fix compile errors in libdeflate 2024-06-01 21:43:41 +03:00
d85a5bcd36 Reapply OpenFBX patches 2024-06-01 21:43:40 +03:00
585ebceb6a Update OpenFBX
Updated to commit 365f52c1edad6bd283c8a645f1d8d2347dbd1e35
2024-06-01 21:43:40 +03:00
593c82543f Limit MSVC compiler code generation threads to one per file 2024-06-01 21:43:39 +03:00
7f1bcd91f5 Remove redundant asset waiting and reloading when saving materials 2024-06-01 21:43:39 +03:00
58214ffc35 Fix RichTextBox not drawing the last character 2024-06-01 21:43:39 +03:00
67b4b01697 _binaries 2024-06-01 21:43:21 +03:00
bd880c0e2f _lfsconfig 2024-06-01 21:43:20 +03:00
0bca21a1d4 Fix compilation errors with miniz 2024-06-01 21:28:23 +03:00
c5e3d4afd3 Update miniz for tinyexr 2024-06-01 21:28:21 +03:00
bb1bee40e4 Fix alignment issues in stack allocators 2024-05-19 23:58:44 +03:00
1b0b8998f9 Hide Visual Studio solution architectures not supported by main project 2024-05-19 23:58:43 +03:00
e4764c4d84 Skip setup of ARM64 configuration for Windows with no compiler support 2024-05-19 23:58:43 +03:00
2de756f761 Include configuration specific source files in solution configurations
Include only relevant generated source files for selected solution
configuration. Fixes Intellisense issues when both ARM64 and Win64
configurations are present in project.
2024-05-19 23:58:43 +03:00
83f40be4f5 Copy hostfxr from platform architecture specific dependencies folder 2024-05-19 23:58:43 +03:00
87a73c9b73 Add cooking support for Windows on ARM 2024-05-19 23:58:42 +03:00
7054f942f6 Update minimp3 2024-05-19 23:58:42 +03:00
227eaff9e2 Patch rapidjson for Windows on ARM 2024-05-19 23:58:42 +03:00
563a45633f Patch tracy for Windows on ARM 2024-05-19 23:58:42 +03:00
09e0754902 Compile glslang for Windows on ARM 2024-05-19 23:58:41 +03:00
3dfe0e6c5a Compile assimp for Windows on ARM 2024-05-19 23:58:41 +03:00
9e7af72046 Add dependencies to copy dbghelp and dxcompiler files from SDK 2024-05-19 23:58:41 +03:00
96eb8cb0ca Build astc for Windows on ARM 2024-05-19 23:58:41 +03:00
ff86057a0a Update OpenAL to use CMake for compilation on Windows 2024-05-19 23:58:40 +03:00
56abd82c9b Update ogg and vorbis to use CMake for compilation on Windows 2024-05-19 23:58:40 +03:00
9777bef9df Patch pix3.h for Windows on ARM 2024-05-19 23:58:40 +03:00
9fedacb404 Use VS2022 Win10 MSVC solutions in DirectX-related dependencies 2024-05-19 23:58:40 +03:00
84f7fde753 Build NvCloth for Windows on ARM 2024-05-19 23:58:39 +03:00
a5566d297f Update PhysX with Windows on ARM support 2024-05-19 23:58:39 +03:00
3f299f99cd Update curl to 7.88.1 2024-05-19 23:58:39 +03:00
741fc959e6 Update Freetype to 2.13.2 2024-05-19 23:58:38 +03:00
93fd560723 Fix rebuilding dependencies using Git with existing local folders 2024-05-19 23:58:38 +03:00
cf48ce6d93 Fallback to D3D11 devices without debug layers when unavailable 2024-05-19 23:58:38 +03:00
bdd3b754bd Support using native host MSVC binaries on ARM64 2024-05-19 23:58:38 +03:00
99c27c9c30 Support ARM64 architecture under Windows 2024-05-19 23:58:37 +03:00
Wojtek Figat
97be8ee8cc Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts:
#	Source/Engine/Content/Storage/FlaxStorage.cpp
#	Source/Engine/Renderer/GBufferPass.cpp
2024-05-15 23:49:05 +02:00
Wojtek Figat
1d6e8c4b7c Add video support on Android 2024-05-15 23:39:10 +02:00
Wojtek Figat
82bf4238df Add support for decoding NV12 into RGB image 2024-05-15 11:15:19 +02:00
Wojtek Figat
9d2dc91920 Add PixelFormat::NV12 2024-05-15 11:14:16 +02:00
Wojtek Figat
3ae30a59b3 Fix engine with c# scripting disabled 2024-05-14 13:13:37 +02:00
Wojtek Figat
a742ce1d32 Optimize FileReadStream seeking if new position is within the cached buffer 2024-05-13 22:40:27 +02:00
Wojtek Figat
3593f835cd Remove unused property of video player 2024-05-13 15:03:55 +02:00
Wojtek Figat
df086f3b3b Add more backends 2024-05-13 15:03:44 +02:00
Wojtek Figat
6b31d51e31 Add volume, pan and spatial audio options for video playback 2024-05-10 13:54:52 +02:00
Wojtek Figat
f0d143ecaa Fix decoding 480p videos 2024-05-10 13:29:08 +02:00
Wojtek Figat
196aa020fd Fix video playback if fie has no audio track 2024-05-10 13:16:07 +02:00
Wojtek Figat
ffe5105602 Fixes for audio playback in videos 2024-05-10 13:12:07 +02:00
Wojtek Figat
4acaa62a07 Fix crash in AudioBackendOAL::Source_DequeueProcessedBuffers when buffer count is large 2024-05-10 12:52:07 +02:00
Wojtek Figat
e7508538e0 Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts:
#	Flax.flaxproj
2024-05-08 19:27:56 +02:00
Wojtek Figat
33202a74b0 Fix 2024-05-08 19:15:25 +02:00
Wojtek Figat
571ba6773d Optimize win32 filesystem access via static buffer or unlimited path len 2024-05-08 17:28:46 +02:00
Wojtek Figat
40652a0ebc Fix HashSet adding item via move operation 2024-05-08 17:28:14 +02:00
Wojtek Figat
481a6de821 Add support for cooking raw files referenced by assets 2024-05-08 17:27:46 +02:00
Wojtek Figat
66b828ae92 Refactor Asset::GetReferences to support file path references 2024-05-08 15:54:37 +02:00
Wojtek Figat
b91f51fb46 Add async videos update 2024-05-08 12:35:18 +02:00
Wojtek Figat
e51d2dda00 Fix playing 6-channel audio with XAudio2 backend 2024-05-08 10:20:04 +02:00
Wojtek Figat
a11fa46ee2 Add cpu profile events to GPU tasks executor queue 2024-05-08 10:19:39 +02:00
Wojtek Figat
47f3ecbde2 Fix video player shutdown on editor window close 2024-05-08 10:19:22 +02:00
Wojtek Figat
deb2319190 Add audio playback support to video player 2024-05-08 10:19:08 +02:00
Wojtek Figat
4b8970f674 Update assets 2024-05-06 10:41:07 +02:00
Wojtek Figat
f43cd97907 Refactor Audio Backend to not depend on AudioSource object 2024-05-06 10:36:36 +02:00
Wojtek Figat
5b2af6b3d5 Refactor Audio Backend to not depend on AudioListener object 2024-05-04 22:16:20 +02:00
Wojtek Figat
f604503566 Refactor Audio Backend to support single listener only 2024-05-04 21:47:47 +02:00
Wojtek Figat
388a0f4196 Add initial audio buffer support in video player 2024-05-03 14:32:23 +02:00
Wojtek Figat
754ed56119 Add FilePathEditor custom editor for path-based editing asset/url refs with a file picker 2024-05-03 12:26:03 +02:00
Wojtek Figat
dca8e391fa Rollback video thumbnail as generic for now 2024-05-02 18:48:49 +02:00
Wojtek Figat
863794d3c0 Add playback buttons and info label to Video Player actor editor 2024-05-01 01:30:03 +02:00
Wojtek Figat
0d8c9f6626 Add Video module 2024-05-01 01:25:16 +02:00
Wojtek Figat
8a45dda98c Add support for custom file proxies in Editor 2024-05-01 01:05:15 +02:00
Wojtek Figat
0cdce9dba2 Upgrade GDK to 230305 and support v143 MSVC toolset 2024-04-25 23:09:18 +02:00
Wojtek Figat
97078cda7e Fix GPU Tasks queue to be executed on frame start, rather than end 2024-04-25 17:10:39 +02:00
Wojtek Figat
10c47b8c2a Fix missing namespace 2024-04-25 17:09:54 +02:00
Wojtek Figat
3ebf73ec22 Add video texture format YUY2 2024-04-25 10:26:23 +02:00
Wojtek Figat
ebe05d4a51 Refactor RenderToolsDX to support new pixel formats properly 2024-04-24 17:35:58 +02:00
Wojtek Figat
cdeb9a3b15 Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts:
#	Content/Editor/Camera/M_Camera.flax
#	Content/Editor/CubeTexturePreviewMaterial.flax
#	Content/Editor/DebugMaterials/DDGIDebugProbes.flax
#	Content/Editor/DebugMaterials/SingleColor/Decal.flax
#	Content/Editor/DebugMaterials/SingleColor/Particle.flax
#	Content/Editor/DebugMaterials/SingleColor/Surface.flax
#	Content/Editor/DebugMaterials/SingleColor/SurfaceAdditive.flax
#	Content/Editor/DebugMaterials/SingleColor/Terrain.flax
#	Content/Editor/DefaultFontMaterial.flax
#	Content/Editor/Gizmo/FoliageBrushMaterial.flax
#	Content/Editor/Gizmo/Material.flax
#	Content/Editor/Gizmo/MaterialWire.flax
#	Content/Editor/Gizmo/SelectionOutlineMaterial.flax
#	Content/Editor/Gizmo/VertexColorsPreviewMaterial.flax
#	Content/Editor/Highlight Material.flax
#	Content/Editor/Icons/IconsMaterial.flax
#	Content/Editor/IesProfilePreviewMaterial.flax
#	Content/Editor/Particles/Particle Material Color.flax
#	Content/Editor/Particles/Smoke Material.flax
#	Content/Editor/SpriteMaterial.flax
#	Content/Editor/Terrain/Circle Brush Material.flax
#	Content/Editor/Terrain/Highlight Terrain Material.flax
#	Content/Editor/TexturePreviewMaterial.flax
#	Content/Editor/Wires Debug Material.flax
#	Content/Engine/DefaultDeformableMaterial.flax
#	Content/Engine/DefaultMaterial.flax
#	Content/Engine/DefaultTerrainMaterial.flax
#	Content/Engine/SingleColorMaterial.flax
#	Content/Engine/SkyboxMaterial.flax
#	Source/Engine/Graphics/Materials/MaterialShader.h
2024-04-23 10:30:01 +02:00
Wojtek Figat
60e8d73079 Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts:
#	Source/Engine/Renderer/RenderList.cpp
#	Source/Engine/Renderer/RenderList.h
2024-04-17 09:58:59 +02:00
Wojtek Figat
cf23892bd4 Fix sun shadows invalidate when view rotates 2024-04-14 23:51:20 +02:00
Wojtek Figat
25f3cef8c3 Fix artifacts due to light shape culling and use depth test to improve perf 2024-04-14 23:44:08 +02:00
Wojtek Figat
00f2a0b825 Improve doc 2024-04-12 11:29:21 +02:00
Wojtek Figat
7342629a86 Add dynamic resolution for static shadow map tiles limited by current dynamic res 2024-04-11 18:47:32 +02:00
Wojtek Figat
5f860db6a5 Fix typo 2024-04-11 17:34:21 +02:00
Wojtek Figat
6233718b06 Update engine shaders 2024-04-11 17:33:42 +02:00
Wojtek Figat
62444315de Add METERS_TO_UNITS and impl metric units in shadows rendering 2024-04-11 16:38:43 +02:00
Wojtek Figat
a532ea7b42 Add InvalidateShadow for manual shadow cache refresh 2024-04-11 16:34:42 +02:00
Wojtek Figat
803249f126 Minor tweaks 2024-04-11 16:19:35 +02:00
Wojtek Figat
4e65b76b8c Optimize BoundingSphere.Intersects to be inlined by the compiler 2024-04-11 15:58:34 +02:00
Wojtek Figat
890b2da108 Add **shadows caching for static geometry** 2024-04-11 15:35:18 +02:00
Wojtek Figat
eac1d19a09 Add additional StaticFlagsCompare to Render View for dynamic or static only drawing 2024-04-11 10:21:13 +02:00
Wojtek Figat
c4949de28f Add new Static Flag Shadow for cached shadow maps 2024-04-11 10:20:21 +02:00
Wojtek Figat
340ef194d3 Add grey out to obsolete/deprecated members in properties panel 2024-04-10 13:36:59 +02:00
Wojtek Figat
b4547ec4d2 Minor fixes 2024-04-10 11:03:33 +02:00
Wojtek Figat
89f7e442f7 Fix point light seams due to missing shadow map borders 2024-04-10 11:03:18 +02:00
Wojtek Figat
e7bef5e880 Bring back Optimized PCF sampling for shadow maps
61323f8526
2024-04-09 17:55:29 +02:00
Wojtek Figat
ff7c986fb1 Add better stability to Cascaded Shadow Maps projection 2024-04-09 16:58:22 +02:00
Wojtek Figat
708fba5136 Add variable rate update for shadow maps atlas based on distance to light 2024-04-08 00:04:57 +02:00
Wojtek Figat
7d92779e99 Merge remote-tracking branch 'origin/master' into 1.9 2024-04-05 21:50:42 +02:00
Wojtek Figat
4c8528dcae Remove branch macro as it's just texture sample in lights shader 2024-04-05 12:48:34 +02:00
Wojtek Figat
3efd1e4e84 Optimize local lights sphere mesh rendering to match the area better 2024-04-05 12:48:09 +02:00
Wojtek Figat
0cc6669cbd Reimplement cascaded shadow maps blending via dithering 2024-04-05 10:59:34 +02:00
Wojtek Figat
8bd409e95d DIsable certain shader features in Volumetric Fog shader 2024-04-04 14:35:22 +02:00
Wojtek Figat
3d0d41ebff Add reducing shadows quality for smaller local lights 2024-04-04 13:29:38 +02:00
Wojtek Figat
61323f8526 Refactor shadows rendering to use Shadow Map Atlas 2024-04-04 12:54:07 +02:00
Wojtek Figat
017def29d4 Rename ShadowSamplerPCF to ShadowSamplerLinear 2024-04-03 13:29:45 +02:00
Wojtek Figat
13a04c2941 Add stencilValue for stencil buffer clearing 2024-04-02 14:56:26 +02:00
Wojtek Figat
bc9cdf5cdb Update version 2024-04-02 14:34:43 +02:00
Wojtek Figat
f7470af42d Optimize depth pass rendering to batch simple materials together 2024-03-26 18:04:08 +01:00
Wojtek Figat
5c356ec22a Fix Global Surface Atlas defragmentation flicker when atlas it nearly full 2024-03-26 16:50:58 +01:00
Wojtek Figat
06a35da0a8 Merge remote-tracking branch 'origin/master' into 1.9 2024-03-26 16:45:31 +01:00
Wojtek Figat
55af307c43 Optimize env probes data storage in renderer 2024-03-26 15:01:12 +01:00
Wojtek Figat
4ab572426d Various renamings 2024-03-26 14:27:10 +01:00
Wojtek Figat
01d91bf102 Optimize decals rendering 2024-03-26 14:05:24 +01:00
Wojtek Figat
2dfb1058b2 Optimize world matrix storage for drawable objects to use Matrix3x4 instead of full matrix 2024-03-26 11:29:01 +01:00
Wojtek Figat
cdbb2cc813 Refactor shader structures naming with a prefix 2024-03-25 17:52:48 +01:00
Wojtek Figat
0e00f1e0eb Refactor lights data in renderer storage 2024-03-25 17:13:40 +01:00
Wojtek Figat
d13621e631 Skip CloseFileHandles if file is not in use 2024-03-25 16:50:48 +01:00
576 changed files with 30076 additions and 10958 deletions

View File

@@ -1,4 +1,4 @@
# Redirect to our own Git LFS server # Redirect to our own Git LFS server
[lfs] [lfs]
url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs" #url="https://gitlab.flaxengine.com/flax/flaxengine.git/info/lfs"
locksverify = false locksverify = false

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -16,7 +16,6 @@
#include "./Flax/ExponentialHeightFog.hlsl" #include "./Flax/ExponentialHeightFog.hlsl"
@2// Forward Shading: Constants @2// Forward Shading: Constants
LightData DirectionalLight; LightData DirectionalLight;
LightShadowData DirectionalLightShadow;
LightData SkyLight; LightData SkyLight;
ProbeData EnvironmentProbe; ProbeData EnvironmentProbe;
ExponentialHeightFogData ExponentialHeightFog; ExponentialHeightFogData ExponentialHeightFog;
@@ -26,9 +25,9 @@ LightData LocalLights[MAX_LOCAL_LIGHTS];
@3// Forward Shading: Resources @3// Forward Shading: Resources
TextureCube EnvProbe : register(t__SRV__); TextureCube EnvProbe : register(t__SRV__);
TextureCube SkyLightTexture : register(t__SRV__); TextureCube SkyLightTexture : register(t__SRV__);
Texture2DArray DirectionalLightShadowMap : register(t__SRV__); Buffer<float4> ShadowsBuffer : register(t__SRV__);
Texture2D<float> ShadowMap : register(t__SRV__);
@4// Forward Shading: Utilities @4// Forward Shading: Utilities
DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow);
@5// Forward Shading: Shaders @5// Forward Shading: Shaders
// Pixel Shader function for Forward Pass // Pixel Shader function for Forward Pass
@@ -80,11 +79,8 @@ void PS_Forward(
// Calculate lighting from a single directional light // Calculate lighting from a single directional light
float4 shadowMask = 1.0f; float4 shadowMask = 1.0f;
if (DirectionalLight.CastShadows > 0) ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer);
{ shadowMask = GetShadowMask(shadow);
LightShadowData directionalLightShadowData = GetDirectionalLightShadowData();
shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g);
}
float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false);
// Calculate lighting from sky light // Calculate lighting from sky light

View File

@@ -26,7 +26,7 @@ struct RibbonInput
// Primary constant buffer (with additional material parameters) // Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float4x4 WorldMatrix; float4x3 WorldMatrix;
uint SortedIndicesOffset; uint SortedIndicesOffset;
float PerInstanceRandom; float PerInstanceRandom;
int ParticleStride; int ParticleStride;
@@ -45,7 +45,7 @@ int RibbonWidthOffset;
int RibbonTwistOffset; int RibbonTwistOffset;
int RibbonFacingVectorOffset; int RibbonFacingVectorOffset;
uint RibbonSegmentCount; uint RibbonSegmentCount;
float4x4 WorldMatrixInverseTransposed; float4x3 WorldMatrixInverseTransposed;
@1META_CB_END @1META_CB_END
// Particles attributes buffer // Particles attributes buffer
@@ -138,7 +138,7 @@ MaterialInput GetMaterialInput(PixelInput input)
#if USE_INSTANCING #if USE_INSTANCING
#define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f)) #define GetInstanceTransform(input) float4x4(float4(input.InstanceTransform1.xyz, 0.0f), float4(input.InstanceTransform2.xyz, 0.0f), float4(input.InstanceTransform3.xyz, 0.0f), float4(input.InstanceOrigin.xyz, 1.0f))
#else #else
#define GetInstanceTransform(input) WorldMatrix; #define GetInstanceTransform(input) ToMatrix4x4(WorldMatrix);
#endif #endif
// Removes the scale vector from the local to world transformation matrix (supports instancing) // Removes the scale vector from the local to world transformation matrix (supports instancing)
@@ -264,12 +264,12 @@ float4 GetParticleVec4(uint particleIndex, int offset)
float3 TransformParticlePosition(float3 input) float3 TransformParticlePosition(float3 input)
{ {
return mul(float4(input, 1.0f), WorldMatrix).xyz; return mul(float4(input, 1.0f), ToMatrix4x4(WorldMatrix)).xyz;
} }
float3 TransformParticleVector(float3 input) float3 TransformParticleVector(float3 input)
{ {
return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz; return mul(float4(input, 0.0f), ToMatrix4x4(WorldMatrixInverseTransposed)).xyz;
} }
@8 @8
@@ -333,7 +333,7 @@ VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID)
float2 spriteSize = GetParticleVec2(particleIndex, SpriteSizeOffset); float2 spriteSize = GetParticleVec2(particleIndex, SpriteSizeOffset);
int spriteFacingMode = SpriteFacingModeOffset != -1 ? GetParticleInt(particleIndex, SpriteFacingModeOffset) : -1; int spriteFacingMode = SpriteFacingModeOffset != -1 ? GetParticleInt(particleIndex, SpriteFacingModeOffset) : -1;
float4x4 world = WorldMatrix; float4x4 world = ToMatrix4x4(WorldMatrix);
float3x3 eulerMatrix = EulerMatrix(radians(particleRotation)); float3x3 eulerMatrix = EulerMatrix(radians(particleRotation));
float3x3 viewRot = transpose((float3x3)ViewMatrix); float3x3 viewRot = transpose((float3x3)ViewMatrix);
float3 position = mul(float4(particlePosition, 1), world).xyz; float3 position = mul(float4(particlePosition, 1), world).xyz;
@@ -463,11 +463,12 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID)
} }
// Read particle data // Read particle data
float4x4 worldMatrix = ToMatrix4x4(WorldMatrix);
float3 particlePosition = GetParticleVec3(particleIndex, PositionOffset); float3 particlePosition = GetParticleVec3(particleIndex, PositionOffset);
float3 particleScale = GetParticleVec3(particleIndex, ScaleOffset); float3 particleScale = GetParticleVec3(particleIndex, ScaleOffset);
float3 particleRotation = GetParticleVec3(particleIndex, RotationOffset); float3 particleRotation = GetParticleVec3(particleIndex, RotationOffset);
int modelFacingMode = ModelFacingModeOffset != -1 ? GetParticleInt(particleIndex, ModelFacingModeOffset) : -1; int modelFacingMode = ModelFacingModeOffset != -1 ? GetParticleInt(particleIndex, ModelFacingModeOffset) : -1;
float3 position = mul(float4(particlePosition, 1), WorldMatrix).xyz; float3 position = mul(float4(particlePosition, 1), worldMatrix).xyz;
// Compute final vertex position in the world // Compute final vertex position in the world
float3x3 eulerMatrix = EulerMatrix(radians(particleRotation)); float3x3 eulerMatrix = EulerMatrix(radians(particleRotation));
@@ -506,7 +507,7 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID)
world = mul(world, scaleMatrix); world = mul(world, scaleMatrix);
} }
world = transpose(world); world = transpose(world);
world = mul(world, WorldMatrix); world = mul(world, worldMatrix);
// Calculate the vertex position in world space // Calculate the vertex position in world space
output.WorldPosition = mul(float4(input.Position, 1), world).xyz; output.WorldPosition = mul(float4(input.Position, 1), world).xyz;
@@ -520,12 +521,12 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID)
#if USE_VERTEX_COLOR #if USE_VERTEX_COLOR
output.VertexColor = input.Color; output.VertexColor = input.Color;
#endif #endif
output.InstanceOrigin = WorldMatrix[3].xyz; output.InstanceOrigin = worldMatrix[3].xyz;
output.InstanceParams = PerInstanceRandom; output.InstanceParams = PerInstanceRandom;
// Calculate tanget space to world space transformation matrix for unit vectors // Calculate tanget space to world space transformation matrix for unit vectors
half3x3 tangentToLocal = CalcTangentToLocal(input); half3x3 tangentToLocal = CalcTangentToLocal(input);
half3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); half3x3 tangentToWorld = CalcTangentToWorld(worldMatrix, tangentToLocal);
output.TBN = tangentToWorld; output.TBN = tangentToWorld;
// Get material input params if need to evaluate any material property // Get material input params if need to evaluate any material property
@@ -625,12 +626,13 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID)
#if USE_VERTEX_COLOR #if USE_VERTEX_COLOR
output.VertexColor = 1; output.VertexColor = 1;
#endif #endif
output.InstanceOrigin = WorldMatrix[3].xyz; float4x4 world = ToMatrix4x4(WorldMatrix);
output.InstanceOrigin = world[3].xyz;
output.InstanceParams = PerInstanceRandom; output.InstanceParams = PerInstanceRandom;
// Calculate tanget space to world space transformation matrix for unit vectors // Calculate tanget space to world space transformation matrix for unit vectors
half3x3 tangentToLocal = float3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp)); half3x3 tangentToLocal = float3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp));
half3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal);
output.TBN = tangentToWorld; output.TBN = tangentToWorld;
// Get material input params if need to evaluate any material property // Get material input params if need to evaluate any material property

View File

@@ -10,8 +10,8 @@
@7 @7
// Primary constant buffer (with additional material parameters) // Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float4x4 WorldMatrix; float4x3 WorldMatrix;
float4x4 PrevWorldMatrix; float4x3 PrevWorldMatrix;
float2 Dummy0; float2 Dummy0;
float LODDitherFactor; float LODDitherFactor;
float PerInstanceRandom; float PerInstanceRandom;
@@ -171,7 +171,7 @@ MaterialInput GetMaterialInput(PixelInput input)
#if USE_INSTANCING #if USE_INSTANCING
#define CalculateInstanceTransform(input) float4x4 world = GetInstanceTransform(input); output.Geometry.InstanceTransform1 = input.InstanceTransform1.xyz; output.Geometry.InstanceTransform2 = input.InstanceTransform2.xyz; output.Geometry.InstanceTransform3 = input.InstanceTransform3.xyz; #define CalculateInstanceTransform(input) float4x4 world = GetInstanceTransform(input); output.Geometry.InstanceTransform1 = input.InstanceTransform1.xyz; output.Geometry.InstanceTransform2 = input.InstanceTransform2.xyz; output.Geometry.InstanceTransform3 = input.InstanceTransform3.xyz;
#else #else
#define CalculateInstanceTransform(input) float4x4 world = WorldMatrix; output.Geometry.InstanceTransform1 = world[0].xyz; output.Geometry.InstanceTransform2 = world[1].xyz; output.Geometry.InstanceTransform3 = world[2].xyz; #define CalculateInstanceTransform(input) float4x4 world = ToMatrix4x4(WorldMatrix); output.Geometry.InstanceTransform1 = world[0].xyz; output.Geometry.InstanceTransform2 = world[1].xyz; output.Geometry.InstanceTransform3 = world[2].xyz;
#endif #endif
// Removes the scale vector from the local to world transformation matrix (supports instancing) // Removes the scale vector from the local to world transformation matrix (supports instancing)
@@ -328,7 +328,7 @@ VertexOutput VS(ModelInput input)
// Compute world space vertex position // Compute world space vertex position
CalculateInstanceTransform(input); CalculateInstanceTransform(input);
output.Geometry.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz; output.Geometry.WorldPosition = mul(float4(input.Position.xyz, 1), world).xyz;
output.Geometry.PrevWorldPosition = mul(float4(input.Position.xyz, 1), PrevWorldMatrix).xyz; output.Geometry.PrevWorldPosition = mul(float4(input.Position.xyz, 1), ToMatrix4x4(PrevWorldMatrix)).xyz;
// Compute clip space position // Compute clip space position
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
@@ -402,7 +402,7 @@ float4 VS_Depth(ModelInput_PosOnly input) : SV_Position
#if USE_INSTANCING #if USE_INSTANCING
float4x4 world = GetInstanceTransform(input); float4x4 world = GetInstanceTransform(input);
#else #else
float4x4 world = WorldMatrix; float4x4 world = ToMatrix4x4(WorldMatrix);
#endif #endif
float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz; float3 worldPosition = mul(float4(input.Position.xyz, 1), world).xyz;
float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix);
@@ -508,9 +508,9 @@ VertexOutput VS_Skinned(ModelInput_Skinned input)
output.Geometry.WorldPosition = mul(float4(position, 1), world).xyz; output.Geometry.WorldPosition = mul(float4(position, 1), world).xyz;
#if PER_BONE_MOTION_BLUR #if PER_BONE_MOTION_BLUR
float3 prevPosition = SkinPrevPosition(input); float3 prevPosition = SkinPrevPosition(input);
output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), PrevWorldMatrix).xyz; output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), ToMatrix4x4(PrevWorldMatrix)).xyz;
#else #else
output.Geometry.PrevWorldPosition = mul(float4(position, 1), PrevWorldMatrix).xyz; output.Geometry.PrevWorldPosition = mul(float4(position, 1), ToMatrix4x4(PrevWorldMatrix)).xyz;
#endif #endif
// Compute clip space position // Compute clip space position

View File

@@ -17,7 +17,7 @@
@7 @7
// Primary constant buffer (with additional material parameters) // Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float4x4 WorldMatrix; float4x3 WorldMatrix;
float3 WorldInvScale; float3 WorldInvScale;
float WorldDeterminantSign; float WorldDeterminantSign;
float PerInstanceRandom; float PerInstanceRandom;
@@ -194,7 +194,7 @@ float3 TransformViewVectorToWorld(MaterialInput input, float3 viewVector)
// Transforms a vector from local space to world space // Transforms a vector from local space to world space
float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector) float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector)
{ {
float3x3 localToWorld = (float3x3)WorldMatrix; float3x3 localToWorld = (float3x3)ToMatrix4x4(WorldMatrix);
//localToWorld = RemoveScaleFromLocalToWorld(localToWorld); //localToWorld = RemoveScaleFromLocalToWorld(localToWorld);
return mul(localVector, localToWorld); return mul(localVector, localToWorld);
} }
@@ -202,7 +202,7 @@ float3 TransformLocalVectorToWorld(MaterialInput input, float3 localVector)
// Transforms a vector from local space to world space // Transforms a vector from local space to world space
float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector) float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector)
{ {
float3x3 localToWorld = (float3x3)WorldMatrix; float3x3 localToWorld = (float3x3)ToMatrix4x4(WorldMatrix);
//localToWorld = RemoveScaleFromLocalToWorld(localToWorld); //localToWorld = RemoveScaleFromLocalToWorld(localToWorld);
return mul(localToWorld, worldVector); return mul(localToWorld, worldVector);
} }
@@ -210,7 +210,7 @@ float3 TransformWorldVectorToLocal(MaterialInput input, float3 worldVector)
// Gets the current object position // Gets the current object position
float3 GetObjectPosition(MaterialInput input) float3 GetObjectPosition(MaterialInput input)
{ {
return WorldMatrix[3].xyz; return ToMatrix4x4(WorldMatrix)[3].xyz;
} }
// Gets the current object size // Gets the current object size
@@ -365,7 +365,8 @@ VertexOutput VS(TerrainVertexInput input)
float3 position = float3(positionXZ.x, height, positionXZ.y); float3 position = float3(positionXZ.x, height, positionXZ.y);
// Compute world space vertex position // Compute world space vertex position
output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; float4x4 worldMatrix = ToMatrix4x4(WorldMatrix);
output.Geometry.WorldPosition = mul(float4(position, 1), worldMatrix).xyz;
// Compute clip space position // Compute clip space position
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
@@ -389,7 +390,7 @@ VertexOutput VS(TerrainVertexInput input)
// Compute world space normal vector // Compute world space normal vector
float3x3 tangentToLocal = CalcTangentBasisFromWorldNormal(normal); float3x3 tangentToLocal = CalcTangentBasisFromWorldNormal(normal);
float3x3 tangentToWorld = CalcTangentToWorld(WorldMatrix, tangentToLocal); float3x3 tangentToWorld = CalcTangentToWorld(worldMatrix, tangentToLocal);
output.Geometry.WorldNormal = tangentToWorld[2]; output.Geometry.WorldNormal = tangentToWorld[2];
// Get material input params if need to evaluate any material property // Get material input params if need to evaluate any material property

View File

@@ -13,8 +13,8 @@
// Primary constant buffer (with additional material parameters) // Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float4x4 InverseViewProjectionMatrix; float4x4 InverseViewProjectionMatrix;
float4x4 WorldMatrix; float4x3 WorldMatrix;
float4x4 WorldMatrixInverseTransposed; float4x3 WorldMatrixInverseTransposed;
float3 GridSize; float3 GridSize;
float PerInstanceRandom; float PerInstanceRandom;
float Dummy0; float Dummy0;
@@ -49,7 +49,7 @@ struct MaterialInput
#endif #endif
}; };
#define GetInstanceTransform(input) WorldMatrix; #define GetInstanceTransform(input) ToMatrix4x4(WorldMatrix);
// Removes the scale vector from the local to world transformation matrix (supports instancing) // Removes the scale vector from the local to world transformation matrix (supports instancing)
float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld)
@@ -170,12 +170,12 @@ float4 GetParticleVec4(uint particleIndex, int offset)
float3 TransformParticlePosition(float3 input) float3 TransformParticlePosition(float3 input)
{ {
return mul(float4(input, 1.0f), WorldMatrix).xyz; return mul(float4(input, 1.0f), ToMatrix4x4(WorldMatrix)).xyz;
} }
float3 TransformParticleVector(float3 input) float3 TransformParticleVector(float3 input)
{ {
return mul(float4(input, 0.0f), WorldMatrixInverseTransposed).xyz; return mul(float4(input, 0.0f), ToMatrix4x4(WorldMatrixInverseTransposed)).xyz;
} }
@8 @8
@@ -219,7 +219,7 @@ void PS_VolumetricFog(Quad_GS2PS input, out float4 VBufferA : SV_Target0, out fl
materialInput.ParticleIndex = ParticleIndex; materialInput.ParticleIndex = ParticleIndex;
materialInput.TBN = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1)); materialInput.TBN = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
materialInput.TwoSidedSign = 1.0f; materialInput.TwoSidedSign = 1.0f;
materialInput.InstanceOrigin = WorldMatrix[3].xyz; materialInput.InstanceOrigin = ToMatrix4x4(WorldMatrix)[3].xyz;
materialInput.InstanceParams = PerInstanceRandom; materialInput.InstanceParams = PerInstanceRandom;
materialInput.SvPosition = clipPos; materialInput.SvPosition = clipPos;
Material material = GetMaterialPS(materialInput); Material material = GetMaterialPS(materialInput);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,9 +2,9 @@
"Name": "Flax", "Name": "Flax",
"Version": { "Version": {
"Major": 1, "Major": 1,
"Minor": 8, "Minor": 9,
"Revision": 1, "Revision": 0,
"Build": 6511 "Build": 6601
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",

View File

@@ -14,8 +14,8 @@ call "Development\Scripts\Windows\CallBuildTool.bat" -genproject %*
if errorlevel 1 goto BuildToolFailed if errorlevel 1 goto BuildToolFailed
:: Build bindings for all editor configurations :: Build bindings for all editor configurations
echo Building C# bindings... ::echo Building C# bindings...
Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor ::Binaries\Tools\Flax.Build.exe -build -BuildBindingsOnly -arch=x64 -platform=Windows --buildTargets=FlaxEditor
popd popd
echo Done! echo Done!

View File

@@ -1,3 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System; using System;
using System.IO; using System.IO;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
@@ -94,30 +96,8 @@ public class AssetPickerValidator : IContentItemOwner
/// </summary> /// </summary>
public string SelectedPath public string SelectedPath
{ {
get get => Utilities.Utils.ToPathProject(_selectedItem?.Path ?? _selected?.Path);
{ set => SelectedItem = string.IsNullOrEmpty(value) ? null : Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(value));
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
} }
/// <summary> /// <summary>
@@ -242,7 +222,7 @@ public class AssetPickerValidator : IContentItemOwner
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class. /// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary> /// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param> /// <param name="assetType">The asset types that this picker accepts.</param>
public AssetPickerValidator(ScriptType assetType) public AssetPickerValidator(ScriptType assetType)
{ {
_type = assetType; _type = assetType;

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// Content item that contains video media file.
/// </summary>
/// <seealso cref="FlaxEditor.Content.JsonAssetItem" />
public sealed class VideoItem : FileItem
{
/// <summary>
/// Initializes a new instance of the <see cref="VideoItem"/> class.
/// </summary>
/// <param name="path">The file path.</param>
public VideoItem(string path)
: base(path)
{
}
/// <inheritdoc />
public override string TypeDescription => "Video";
/// <inheritdoc />
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
}
}

View File

@@ -30,9 +30,7 @@ namespace FlaxEditor.Content
/// <summary> /// <summary>
/// Determines whether [is virtual proxy]. /// Determines whether [is virtual proxy].
/// </summary> /// </summary>
/// <returns> /// <returns><c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.</returns>
/// <c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.
/// </returns>
public bool IsVirtualProxy() public bool IsVirtualProxy()
{ {
return IsVirtual && CanExport == false; return IsVirtual && CanExport == false;

View File

@@ -29,6 +29,12 @@ namespace FlaxEditor.Content
return item is CSharpScriptItem; return item is CSharpScriptItem;
} }
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CSharpScriptItem(path);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) public override void Create(string outputPath, object arg)
{ {

View File

@@ -39,6 +39,16 @@ namespace FlaxEditor.Content
return false; return false;
} }
/// <summary>
/// Constructs the item for the file.
/// </summary>
/// <param name="path">The file path.</param>
/// <returns>Created item or null.</returns>
public virtual ContentItem ConstructItem(string path)
{
return null;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this proxy if for assets. /// Gets a value indicating whether this proxy if for assets.
/// </summary> /// </summary>

View File

@@ -87,6 +87,12 @@ namespace FlaxEditor.Content
return item is CppScriptItem; return item is CppScriptItem;
} }
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CppScriptItem(path);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate) protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
{ {

View File

@@ -20,6 +20,12 @@ namespace FlaxEditor.Content
return item is FileItem; return item is FileItem;
} }
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new FileItem(path);
}
/// <inheritdoc /> /// <inheritdoc />
public override string FileExtension => string.Empty; public override string FileExtension => string.Empty;

View File

@@ -73,6 +73,16 @@ namespace FlaxEditor.Content
return targetLocation.CanHaveAssets; return targetLocation.CanHaveAssets;
} }
/// <inheritdoc />
public override bool CanReimport(ContentItem item)
{
if (item is not PrefabItem prefabItem)
return base.CanReimport(item);
var prefab = FlaxEngine.Content.Load<Prefab>(prefabItem.ID);
return prefab.GetDefaultInstance().GetScript<ModelPrefab>() != null;
}
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) public override void Create(string outputPath, object arg)
{ {

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// A video media file proxy object.
/// </summary>
public class VideoProxy : ContentProxy
{
private readonly string _extension;
internal VideoProxy(string extension)
{
_extension = extension;
}
/// <inheritdoc />
public override string Name => "Video";
/// <inheritdoc />
public override string FileExtension => _extension;
/// <inheritdoc />
public override Color AccentColor => Color.FromRGB(0x11f7f1);
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return item is VideoItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new VideoItem(path);
}
/// <inheritdoc />
public override EditorWindow Open(Editor editor, ContentItem item)
{
return new VideoWindow(editor, (VideoItem)item);
}
}
}

View File

@@ -134,6 +134,12 @@ API_ENUM() enum class BuildPlatform
/// </summary> /// </summary>
API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")") API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")")
iOSARM64 = 14, iOSARM64 = 14,
/// <summary>
/// Windows (ARM64)
/// </summary>
API_ENUM(Attributes = "EditorDisplay(null, \"Windows ARM64\")")
WindowsARM64 = 15,
}; };
/// <summary> /// <summary>
@@ -285,24 +291,22 @@ public:
/// <summary> /// <summary>
/// The total assets amount in the build. /// The total assets amount in the build.
/// </summary> /// </summary>
int32 TotalAssets; int32 TotalAssets = 0;
/// <summary> /// <summary>
/// The cooked assets (TotalAssets - CookedAssets is amount of reused cached assets). /// The cooked assets (TotalAssets - CookedAssets is amount of reused cached assets).
/// </summary> /// </summary>
int32 CookedAssets; int32 CookedAssets = 0;
/// <summary> /// <summary>
/// The final output content size in MB. /// The final output content size (in bytes).
/// </summary> /// </summary>
int32 ContentSizeMB; uint64 ContentSize = 0;
/// <summary> /// <summary>
/// The asset type stats. Key is the asset typename, value is the stats container. /// The asset type stats. Key is the asset typename, value is the stats container.
/// </summary> /// </summary>
Dictionary<String, AssetTypeStatistics> AssetStats; Dictionary<String, AssetTypeStatistics> AssetStats;
Statistics();
}; };
/// <summary> /// <summary>
@@ -328,6 +332,11 @@ public:
/// </summary> /// </summary>
HashSet<Guid> Assets; HashSet<Guid> Assets;
/// <summary>
/// The final files collection to include in build (valid only after CollectAssetsStep).
/// </summary>
HashSet<String> Files;
struct BinaryModuleInfo struct BinaryModuleInfo
{ {
String Name; String Name;

View File

@@ -148,6 +148,8 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("Mac ARM64"); return TEXT("Mac ARM64");
case BuildPlatform::iOSARM64: case BuildPlatform::iOSARM64:
return TEXT("iOS ARM64"); return TEXT("iOS ARM64");
case BuildPlatform::WindowsARM64:
return TEXT("Windows ARM64");
default: default:
return TEXT(""); return TEXT("");
} }
@@ -202,13 +204,6 @@ bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& othe
return Count > other.Count; return Count > other.Count;
} }
CookingData::Statistics::Statistics()
{
TotalAssets = 0;
CookedAssets = 0;
ContentSizeMB = 0;
}
CookingData::CookingData(const SpawnParams& params) CookingData::CookingData(const SpawnParams& params)
: ScriptingObject(params) : ScriptingObject(params)
{ {
@@ -307,6 +302,10 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
platform = TEXT("iOS"); platform = TEXT("iOS");
architecture = TEXT("ARM64"); architecture = TEXT("ARM64");
break; break;
case BuildPlatform::WindowsARM64:
platform = TEXT("Windows");
architecture = TEXT("ARM64");
break;
default: default:
LOG(Fatal, "Unknown or unsupported build platform."); LOG(Fatal, "Unknown or unsupported build platform.");
} }
@@ -393,6 +392,9 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::Windows64: case BuildPlatform::Windows64:
result = New<WindowsPlatformTools>(ArchitectureType::x64); result = New<WindowsPlatformTools>(ArchitectureType::x64);
break; break;
case BuildPlatform::WindowsARM64:
result = New<WindowsPlatformTools>(ArchitectureType::ARM64);
break;
#endif #endif
#if PLATFORM_TOOLS_UWP #if PLATFORM_TOOLS_UWP
case BuildPlatform::UWPx86: case BuildPlatform::UWPx86:
@@ -554,7 +556,12 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build
switch (PLATFORM_TYPE) switch (PLATFORM_TYPE)
{ {
case PlatformType::Windows: case PlatformType::Windows:
buildPlatform = PLATFORM_64BITS ? BuildPlatform::Windows64 : BuildPlatform::Windows32; if (PLATFORM_ARCH == ArchitectureType::x64)
buildPlatform = BuildPlatform::Windows64;
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
buildPlatform = BuildPlatform::WindowsARM64;
else
buildPlatform = BuildPlatform::Windows32;
break; break;
case PlatformType::XboxOne: case PlatformType::XboxOne:
buildPlatform = BuildPlatform::XboxOne; buildPlatform = BuildPlatform::XboxOne;

View File

@@ -325,9 +325,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const auto buildSettings = BuildSettings::Get(); const auto buildSettings = BuildSettings::Get();
if (buildSettings->SkipPackaging) if (buildSettings->SkipPackaging)
{
return false; return false;
}
GameCooker::PackageFiles(); GameCooker::PackageFiles();
// Validate environment variables // Validate environment variables

View File

@@ -10,47 +10,26 @@
#include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Assets/Shader.h"
#include "Engine/Content/Cache/AssetsCache.h" #include "Engine/Content/Cache/AssetsCache.h"
bool CollectAssetsStep::Process(CookingData& data, Asset* asset)
{
// Skip virtual/temporary assets
if (asset->IsVirtual())
return false;
// Keep reference to the asset
AssetReference<Asset> ref(asset);
// Asset should have loaded data
if (asset->WaitForLoaded())
return false;
// Gather asset references
_references.Clear();
asset->Locker.Lock();
asset->GetReferences(_references);
asset->Locker.Unlock();
_assetsQueue.Add(_references);
return false;
}
bool CollectAssetsStep::Perform(CookingData& data) bool CollectAssetsStep::Perform(CookingData& data)
{ {
LOG(Info, "Searching for assets to include in a build. Using {0} root assets.", data.RootAssets.Count()); LOG(Info, "Searching for assets to include in a build. Using {0} root assets.", data.RootAssets.Count());
data.StepProgress(TEXT("Collecting assets"), 0); data.StepProgress(TEXT("Collecting assets"), 0);
// Initialize assets queue // Initialize assets queue
_assetsQueue.Clear(); Array<Guid> assetsQueue;
_assetsQueue.EnsureCapacity(1024); assetsQueue.Clear();
assetsQueue.EnsureCapacity(1024);
for (auto i = data.RootAssets.Begin(); i.IsNotEnd(); ++i) for (auto i = data.RootAssets.Begin(); i.IsNotEnd(); ++i)
_assetsQueue.Add(i->Item); assetsQueue.Add(i->Item);
// Iterate through the assets graph // Iterate through the assets graph
AssetInfo assetInfo; AssetInfo assetInfo;
while (_assetsQueue.HasItems()) Array<Guid> references;
Array<String> files;
while (assetsQueue.HasItems())
{ {
BUILD_STEP_CANCEL_CHECK; BUILD_STEP_CANCEL_CHECK;
const Guid assetId = assetsQueue.Dequeue();
const auto assetId = _assetsQueue.Dequeue();
// Skip already processed or invalid assets // Skip already processed or invalid assets
if (!assetId.IsValid() if (!assetId.IsValid()
@@ -69,14 +48,31 @@ bool CollectAssetsStep::Perform(CookingData& data)
} }
// Load asset // Load asset
const auto asset = Content::LoadAsync<Asset>(assetId); AssetReference<Asset> asset = Content::LoadAsync<Asset>(assetId);
if (asset == nullptr) if (asset == nullptr)
continue; continue;
// Process that asset
LOG_STR(Info, asset->GetPath()); LOG_STR(Info, asset->GetPath());
data.Assets.Add(assetId); data.Assets.Add(assetId);
Process(data, asset);
// Skip virtual/temporary assets
if (asset->IsVirtual())
continue;
// Asset should have loaded data
if (asset->WaitForLoaded())
continue;
// Gather asset references
references.Clear();
asset->Locker.Lock();
asset->GetReferences(references, files);
asset->Locker.Unlock();
assetsQueue.Add(references);
for (String& file : files)
{
if (file.HasChars())
data.Files.Add(MoveTemp(file));
}
} }
data.Stats.TotalAssets = data.Assets.Count(); data.Stats.TotalAssets = data.Assets.Count();

View File

@@ -12,15 +12,7 @@ class Asset;
/// <seealso cref="GameCooker::BuildStep" /> /// <seealso cref="GameCooker::BuildStep" />
class CollectAssetsStep : public GameCooker::BuildStep class CollectAssetsStep : public GameCooker::BuildStep
{ {
private:
Array<Guid> _assetsQueue;
Array<Guid> _references;
bool Process(CookingData& data, Asset* asset);
public: public:
// [BuildStep] // [BuildStep]
bool Perform(CookingData& data) override; bool Perform(CookingData& data) override;
}; };

View File

@@ -447,6 +447,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
#if PLATFORM_TOOLS_WINDOWS #if PLATFORM_TOOLS_WINDOWS
case BuildPlatform::Windows32: case BuildPlatform::Windows32:
case BuildPlatform::Windows64: case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
{ {
const char* platformDefineName = "PLATFORM_WINDOWS"; const char* platformDefineName = "PLATFORM_WINDOWS";
const auto settings = WindowsPlatformSettings::Get(); const auto settings = WindowsPlatformSettings::Get();
@@ -891,7 +892,6 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, JsonAssetBase*
class PackageBuilder : public NonCopyable class PackageBuilder : public NonCopyable
{ {
private: private:
int32 _packageIndex; int32 _packageIndex;
int32 MaxAssetsPerPackage; int32 MaxAssetsPerPackage;
int32 MaxPackageSize; int32 MaxPackageSize;
@@ -904,7 +904,6 @@ private:
uint64 packagesSizeTotal; uint64 packagesSizeTotal;
public: public:
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PackageBuilder" /> class. /// Initializes a new instance of the <see cref="PackageBuilder" /> class.
/// </summary> /// </summary>
@@ -933,7 +932,6 @@ public:
} }
public: public:
uint64 GetPackagesSizeTotal() const uint64 GetPackagesSizeTotal() const
{ {
return packagesSizeTotal; return packagesSizeTotal;
@@ -1042,8 +1040,11 @@ bool CookAssetsStep::Perform(CookingData& data)
float Step1ProgressEnd = 0.6f; float Step1ProgressEnd = 0.6f;
String Step1Info = TEXT("Cooking assets"); String Step1Info = TEXT("Cooking assets");
float Step2ProgressStart = Step1ProgressEnd; float Step2ProgressStart = Step1ProgressEnd;
float Step2ProgressEnd = 0.9f; float Step2ProgressEnd = 0.8f;
String Step2Info = TEXT("Packaging assets"); String Step2Info = TEXT("Cooking files");
float Step3ProgressStart = Step2ProgressStart;
float Step3ProgressEnd = 0.9f;
String Step3Info = TEXT("Packaging assets");
data.StepProgress(TEXT("Loading build cache"), 0); data.StepProgress(TEXT("Loading build cache"), 0);
@@ -1100,11 +1101,14 @@ bool CookAssetsStep::Perform(CookingData& data)
#endif #endif
int32 subStepIndex = 0; int32 subStepIndex = 0;
AssetReference<Asset> assetRef; AssetReference<Asset> assetRef;
assetRef.Unload.Bind([]() { LOG(Error, "Asset gets unloaded while cooking it!"); Platform::Sleep(100); }); assetRef.Unload.Bind([]
{
LOG(Error, "Asset got unloaded while cooking it!");
Platform::Sleep(100);
});
for (auto i = data.Assets.Begin(); i.IsNotEnd(); ++i) for (auto i = data.Assets.Begin(); i.IsNotEnd(); ++i)
{ {
BUILD_STEP_CANCEL_CHECK; BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step1Info, Math::Lerp(Step1ProgressStart, Step1ProgressEnd, static_cast<float>(subStepIndex++) / data.Assets.Count())); data.StepProgress(Step1Info, Math::Lerp(Step1ProgressStart, Step1ProgressEnd, static_cast<float>(subStepIndex++) / data.Assets.Count()));
const Guid assetId = i->Item; const Guid assetId = i->Item;
@@ -1184,6 +1188,35 @@ bool CookAssetsStep::Perform(CookingData& data)
// Save build cache header // Save build cache header
cache.Save(data); cache.Save(data);
// Process all files
for (auto i = data.Files.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, (float)subStepIndex++ / data.Files.Count()));
const String& filePath = i->Item;
// Calculate destination path
String cookedPath = data.DataOutputPath;
if (FileSystem::IsRelative(filePath))
cookedPath /= filePath;
else
cookedPath /= String(TEXT("Content")) / StringUtils::GetFileName(filePath);
// Copy file
if (!FileSystem::FileExists(cookedPath) || FileSystem::GetFileLastEditTime(cookedPath) >= FileSystem::GetFileLastEditTime(filePath))
{
if (FileSystem::CreateDirectory(StringUtils::GetDirectoryName(cookedPath)))
return true;
if (FileSystem::CopyFile(cookedPath, filePath))
return true;
}
// Count stats of file extension
auto& assetStats = data.Stats.AssetStats[FileSystem::GetExtension(cookedPath)];
assetStats.Count++;
assetStats.ContentSize += FileSystem::GetFileSize(cookedPath);
}
// Create build game header // Create build game header
{ {
GameHeaderFlags gameFlags = GameHeaderFlags::None; GameHeaderFlags gameFlags = GameHeaderFlags::None;
@@ -1229,13 +1262,11 @@ bool CookAssetsStep::Perform(CookingData& data)
for (auto i = AssetsRegistry.Begin(); i.IsNotEnd(); ++i) for (auto i = AssetsRegistry.Begin(); i.IsNotEnd(); ++i)
{ {
BUILD_STEP_CANCEL_CHECK; BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step3Info, Math::Lerp(Step3ProgressStart, Step3ProgressEnd, (float)subStepIndex++ / AssetsRegistry.Count()));
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, static_cast<float>(subStepIndex++) / AssetsRegistry.Count()));
const auto assetId = i->Key; const auto assetId = i->Key;
String cookedFilePath; String cookedFilePath;
cache.GetFilePath(assetId, cookedFilePath); cache.GetFilePath(assetId, cookedFilePath);
if (!FileSystem::FileExists(cookedFilePath)) if (!FileSystem::FileExists(cookedFilePath))
{ {
LOG(Warning, "Missing cooked file for asset \'{0}\'", assetId); LOG(Warning, "Missing cooked file for asset \'{0}\'", assetId);
@@ -1253,12 +1284,12 @@ bool CookAssetsStep::Perform(CookingData& data)
return true; return true;
for (auto& e : data.Stats.AssetStats) for (auto& e : data.Stats.AssetStats)
e.Value.TypeName = e.Key; e.Value.TypeName = e.Key;
data.Stats.ContentSizeMB = static_cast<int32>(packageBuilder.GetPackagesSizeTotal() / (1024 * 1024)); data.Stats.ContentSize += packageBuilder.GetPackagesSizeTotal();
} }
BUILD_STEP_CANCEL_CHECK; BUILD_STEP_CANCEL_CHECK;
data.StepProgress(TEXT("Creating assets cache"), Step2ProgressEnd); data.StepProgress(TEXT("Creating assets cache"), Step3ProgressEnd);
// Create asset paths mapping for the assets. // Create asset paths mapping for the assets.
// Assets mapping is use to convert paths used in Content::Load(path) into the asset id. // Assets mapping is use to convert paths used in Content::Load(path) into the asset id.
@@ -1291,7 +1322,7 @@ bool CookAssetsStep::Perform(CookingData& data)
} }
// Print stats // Print stats
LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), data.Stats.ContentSizeMB); LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), (int32)(data.Stats.ContentSize / (1024 * 1024)));
{ {
Array<CookingData::AssetTypeStatistics> assetTypes; Array<CookingData::AssetTypeStatistics> assetTypes;
data.Stats.AssetStats.GetValues(assetTypes); data.Stats.AssetStats.GetValues(assetTypes);

View File

@@ -73,6 +73,7 @@ bool DeployDataStep::Perform(CookingData& data)
{ {
case BuildPlatform::Windows32: case BuildPlatform::Windows32:
case BuildPlatform::Windows64: case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Windows; canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Windows;
break; break;
case BuildPlatform::LinuxX64: case BuildPlatform::LinuxX64:
@@ -159,7 +160,20 @@ bool DeployDataStep::Perform(CookingData& data)
} }
else else
{ {
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true); // TODO: hostfxr for target platform should be copied from nuget package location: microsoft.netcore.app.runtime.<RID>/<VERSION>/runtimes/<RID>/native/hostfxr.dll
String dstHostfxr = dstDotnet / TEXT("host/fxr") / version;
if (!FileSystem::DirectoryExists(dstHostfxr))
FileSystem::CreateDirectory(dstHostfxr);
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
if (data.Platform == BuildPlatform::Windows64 || data.Platform == BuildPlatform::WindowsARM64 || data.Platform == BuildPlatform::Windows32)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dll"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dll"));
else if (data.Platform == BuildPlatform::LinuxX64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.so"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.so"));
else if (data.Platform == BuildPlatform::MacOSx64 || data.Platform == BuildPlatform::MacOSARM64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dylib"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dylib"));
else
failed |= true;
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true); failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
} }
if (failed) if (failed)

View File

@@ -377,6 +377,10 @@ namespace FlaxEditor.CustomEditors
else if (Values.HasDefaultValue && CanRevertDefaultValue) else if (Values.HasDefaultValue && CanRevertDefaultValue)
color = Color.Yellow * 0.8f; color = Color.Yellow * 0.8f;
LinkedLabel.HighlightStripColor = color; LinkedLabel.HighlightStripColor = color;
// Grey out deprecated members
if (Values.IsObsolete)
LinkedLabel.TextColor = LinkedLabel.TextColorHighlighted = FlaxEngine.GUI.Style.Current.ForegroundGrey;
} }
} }

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors namespace FlaxEditor.CustomEditors.Editors
@@ -50,7 +51,6 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentTypes) if (HasDifferentTypes)
return; return;
Picker = layout.Custom<AssetPicker>().CustomControl; Picker = layout.Custom<AssetPicker>().CustomControl;
var value = Values[0]; var value = Values[0];
_valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value); _valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
var assetType = _valueType; var assetType = _valueType;
@@ -58,37 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
assetType = new ScriptType(typeof(Asset)); assetType = new ScriptType(typeof(Asset));
else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name) else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]); assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
float height = 48;
var attributes = Values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
assetType = ScriptType.Null;
Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
assetType = ScriptType.Void;
}
}
Picker.Validator.AssetType = assetType; Picker.Validator.AssetType = assetType;
ApplyAssetReferenceAttribute(Values, out var height, Picker.Validator);
Picker.Height = height; Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged; Picker.SelectedItemChanged += OnSelectedItemChanged;
} }
@@ -115,6 +86,37 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(Picker.Validator.SelectedAsset); SetValue(Picker.Validator.SelectedAsset);
} }
internal static void ApplyAssetReferenceAttribute(ValueContainer values, out float height, AssetPickerValidator validator)
{
height = 48;
var attributes = values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
validator.AssetType = ScriptType.Null;
validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
validator.AssetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
validator.AssetType = ScriptType.Void;
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {
@@ -140,4 +142,155 @@ namespace FlaxEditor.CustomEditors.Editors
} }
} }
} }
/// <summary>
/// Default implementation of the inspector used to edit reference to the files via path (absolute or relative to the project).
/// </summary>
/// <remarks>Supports editing reference to the asset via path using various containers: <see cref="Asset"/> or <see cref="AssetItem"/> or <see cref="System.String"/>.</remarks>
public class FilePathEditor : CustomEditor
{
private sealed class TextBoxWithPicker : TextBox
{
private const float DropdownIconMargin = 3.0f;
private const float DropdownIconSize = 12.0f;
private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize);
public Action ShowPicker;
public override void Draw()
{
base.Draw();
var style = FlaxEngine.GUI.Style.Current;
var dropdownRect = DropdownRect;
Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (DropdownRect.Contains(ref location))
{
Focus();
ShowPicker();
return true;
}
return base.OnMouseDown(location, button);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (DropdownRect.Contains(ref location))
Cursor = CursorType.Default;
else
Cursor = CursorType.IBeam;
}
protected override Rectangle TextRectangle
{
get
{
var result = base.TextRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
protected override Rectangle TextClipRectangle
{
get
{
var result = base.TextClipRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
}
private TextBoxWithPicker _textBox;
private AssetPickerValidator _validator;
private bool _isRefreshing;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
if (HasDifferentTypes)
return;
_textBox = layout.Custom<TextBoxWithPicker>().CustomControl;
_textBox.ShowPicker = OnShowPicker;
_textBox.EditEnd += OnEditEnd;
_validator = new AssetPickerValidator(ScriptType.Null);
AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator);
}
private void OnShowPicker()
{
if (_validator.AssetType != ScriptType.Null)
AssetSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
else
ContentSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
}
private void SetPickerPath(ContentItem item)
{
var path = Utilities.Utils.ToPathProject(item.Path);
SetPath(path);
_isRefreshing = true;
_textBox.Defocus();
_textBox.Text = path;
_isRefreshing = false;
_textBox.RootWindow.Focus();
_textBox.Focus();
}
private void OnEditEnd()
{
SetPath(_textBox.Text);
}
private string GetPath()
{
var value = Values[0];
if (value is AssetItem assetItem)
return Utilities.Utils.ToPathProject(assetItem.Path);
if (value is Asset asset)
return Utilities.Utils.ToPathProject(asset.Path);
if (value is string str)
return str;
return null;
}
private void SetPath(string path)
{
if (_isRefreshing)
return;
var value = Values[0];
if (value is AssetItem)
SetValue(Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(path)));
else if (value is Asset)
SetValue(FlaxEngine.Content.LoadAsync(path));
else if (value is string)
SetValue(path);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (!HasDifferentValues)
{
_isRefreshing = true;
_textBox.Text = GetPath();
_isRefreshing = false;
}
}
}
} }

View File

@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Editors
new OptionType("Linear Gradient", typeof(LinearGradientBrush)), new OptionType("Linear Gradient", typeof(LinearGradientBrush)),
new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)), new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)),
new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)), new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)),
new OptionType("Video", typeof(VideoBrush)),
}; };
} }
} }

View File

@@ -139,6 +139,11 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
public bool IsArray => Type != ScriptType.Null && Type.IsArray; public bool IsArray => Type != ScriptType.Null && Type.IsArray;
/// <summary>
/// True if member or type has <see cref="System.ObsoleteAttribute"/> that marks it as obsolete.
/// </summary>
public bool IsObsolete { get; }
/// <summary> /// <summary>
/// Gets the values types array (without duplicates). /// Gets the values types array (without duplicates).
/// </summary> /// </summary>
@@ -160,6 +165,7 @@ namespace FlaxEditor.CustomEditors
{ {
Info = info; Info = info;
Type = Info.ValueType; Type = Info.ValueType;
IsObsolete = Info.HasAttribute(typeof(ObsoleteAttribute), true);
} }
/// <summary> /// <summary>

View File

@@ -5,10 +5,8 @@ using System.IO;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.GUI namespace FlaxEditor.GUI
{ {

View File

@@ -129,12 +129,9 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++) for (int i = 0; i < Proxy.Count; i++)
{ {
if (Proxy[i].IsProxyFor(item)) if (Proxy[i].IsProxyFor(item))
{
return Proxy[i]; return Proxy[i];
}
} }
} }
return null; return null;
} }
@@ -147,11 +144,8 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++) for (int i = 0; i < Proxy.Count; i++)
{ {
if (Proxy[i].IsProxyFor<T>()) if (Proxy[i].IsProxyFor<T>())
{
return Proxy[i]; return Proxy[i];
}
} }
return null; return null;
} }
@@ -164,17 +158,12 @@ namespace FlaxEditor.Modules
{ {
if (string.IsNullOrEmpty(extension)) if (string.IsNullOrEmpty(extension))
throw new ArgumentNullException(); throw new ArgumentNullException();
extension = StringUtils.NormalizeExtension(extension); extension = StringUtils.NormalizeExtension(extension);
for (int i = 0; i < Proxy.Count; i++) for (int i = 0; i < Proxy.Count; i++)
{ {
if (Proxy[i].FileExtension == extension) if (string.Equals(Proxy[i].FileExtension, extension, StringComparison.Ordinal))
{
return Proxy[i]; return Proxy[i];
}
} }
return null; return null;
} }
@@ -189,30 +178,23 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++) for (int i = 0; i < Proxy.Count; i++)
{ {
if (Proxy[i] is AssetProxy proxy && proxy.AcceptsAsset(typeName, path)) if (Proxy[i] is AssetProxy proxy && proxy.AcceptsAsset(typeName, path))
{
return proxy; return proxy;
}
} }
return null; return null;
} }
/// <summary> /// <summary>
/// Gets the virtual proxy object from given path. /// Gets the virtual proxy object from given path.
/// <br></br>use case if the asset u trying to display is not a flax asset but u like to add custom functionality
/// <br></br>to context menu,or display it the asset
/// </summary> /// </summary>
/// <param name="path">The asset path.</param> /// <param name="path">The asset path.</param>
/// <returns>Asset proxy or null if cannot find.</returns> /// <returns>Asset proxy or null if cannot find.</returns>
public AssetProxy GetAssetVirtuallProxy(string path) public AssetProxy GetAssetVirtualProxy(string path)
{ {
for (int i = 0; i < Proxy.Count; i++) for (int i = 0; i < Proxy.Count; i++)
{ {
if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase)) if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase))
{
return proxy; return proxy;
}
} }
return null; return null;
} }
@@ -1016,11 +998,13 @@ namespace FlaxEditor.Modules
} }
if (item == null) if (item == null)
{ {
var proxy = GetAssetVirtuallProxy(path); var proxy = GetAssetVirtualProxy(path);
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID); item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
if (item == null) if (item == null)
{ {
item = new FileItem(path); item = GetProxy(Path.GetExtension(path))?.ConstructItem(path);
if (item == null)
item = new FileItem(path);
} }
} }
@@ -1106,6 +1090,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new VisualScriptProxy()); Proxy.Add(new VisualScriptProxy());
Proxy.Add(new BehaviorTreeProxy()); Proxy.Add(new BehaviorTreeProxy());
Proxy.Add(new LocalizedStringTableProxy()); Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new VideoProxy("mp4"));
Proxy.Add(new WidgetProxy()); Proxy.Add(new WidgetProxy());
Proxy.Add(new FileProxy()); Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>()); Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());

View File

@@ -0,0 +1,69 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="VideoPlayer"/>.
/// </summary>
[CustomEditor(typeof(VideoPlayer)), DefaultEditor]
public class VideoPlayerEditor : ActorEditor
{
private Label _infoLabel;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
// Show playback options during simulation
if (Editor.IsPlayMode)
{
var playbackGroup = layout.Group("Playback");
playbackGroup.Panel.Open();
_infoLabel = playbackGroup.Label(string.Empty).Label;
_infoLabel.AutoHeight = true;
var grid = playbackGroup.CustomContainer<UniformGridPanel>();
var gridControl = grid.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 3;
gridControl.SlotsVertically = 1;
grid.Button("Play").Button.Clicked += () => Foreach(x => x.Play());
grid.Button("Pause").Button.Clicked += () => Foreach(x => x.Pause());
grid.Button("Stop").Button.Clicked += () => Foreach(x => x.Stop());
}
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (_infoLabel != null)
{
var text = string.Empty;
foreach (var value in Values)
{
if (value is VideoPlayer player)
text += $"Time: {player.Time:##0.0}s / {player.Duration:##0.0}s\nResolution: {player.Size.X}x{player.Size.Y}, Frame Rate: {player.FrameRate}";
}
_infoLabel.Text = text;
}
}
private void Foreach(Action<VideoPlayer> func)
{
foreach (var value in Values)
{
if (value is VideoPlayer player)
func(player);
}
}
}
}

View File

@@ -1471,5 +1471,27 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync()); inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile); inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
} }
internal static string ToPathProject(string path)
{
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
internal static string ToPathAbsolute(string path)
{
if (path != null)
{
// Convert into global path to if relative to the project
path = StringUtils.IsRelative(path) ? Path.Combine(Globals.ProjectFolder, path) : path;
}
return path;
}
} }
} }

View File

@@ -21,6 +21,7 @@
#include "Engine/Level/Actors/Sky.h" #include "Engine/Level/Actors/Sky.h"
#include "Engine/Level/Actors/SkyLight.h" #include "Engine/Level/Actors/SkyLight.h"
#include "Engine/Level/Actors/SpotLight.h" #include "Engine/Level/Actors/SpotLight.h"
#include "Engine/Video/VideoPlayer.h"
#define ICON_RADIUS 7.0f #define ICON_RADIUS 7.0f
@@ -283,6 +284,7 @@ bool ViewportIconsRendererService::Init()
MAP_TYPE(Sky, Skybox); MAP_TYPE(Sky, Skybox);
MAP_TYPE(SkyLight, SkyLight); MAP_TYPE(SkyLight, SkyLight);
MAP_TYPE(SpotLight, PointLight); MAP_TYPE(SpotLight, PointLight);
MAP_TYPE(VideoPlayer, SceneAnimationPlayer);
#undef MAP_TYPE #undef MAP_TYPE
return false; return false;

View File

@@ -459,8 +459,7 @@ namespace FlaxEditor.Windows.Assets
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
protected virtual bool SaveToOriginal() protected virtual bool SaveToOriginal()
{ {
// Wait until temporary asset file be fully loaded if (_asset.LastLoadFailed)
if (_asset.WaitForLoaded())
{ {
Editor.LogError(string.Format("Cannot save asset {0}. Wait for temporary asset loaded failed.", _item.Path)); Editor.LogError(string.Format("Cannot save asset {0}. Wait for temporary asset loaded failed.", _item.Path));
return true; return true;
@@ -494,12 +493,6 @@ namespace FlaxEditor.Windows.Assets
return true; return true;
} }
// Reload original asset
if (originalAsset)
{
originalAsset.Reload();
}
// Refresh thumbnail // Refresh thumbnail
_item.RefreshThumbnail(); _item.RefreshThumbnail();

View File

@@ -355,7 +355,6 @@ namespace FlaxEditor.Windows.Assets
Editor.LogError("Failed to save surface data"); Editor.LogError("Failed to save surface data");
} }
_asset.Reload(); _asset.Reload();
_asset.WaitForLoaded();
} }
} }

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Content;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Assets
{
/// <summary>
/// Editor window to view video media.
/// </summary>
public sealed class VideoWindow : EditorWindow, IContentItemOwner
{
private VideoItem _item;
private Image _frame;
private VideoPlayer _videoPlayer;
private Image _seekBegin, _seekEnd, _seekLeft, _seekRight, _playPause, _stop;
/// <inheritdoc />
public VideoWindow(Editor editor, VideoItem item)
: base(editor, false, ScrollBars.None)
{
_item = item;
_item.AddReference(this);
Title = _item.ShortName;
// Setup video player
_videoPlayer = new VideoPlayer
{
PlayOnStart = false,
Url = item.Path,
};
// Setup UI
var style = Style.Current;
var icons = Editor.Icons;
var playbackButtonsSize = 24.0f;
var playbackButtonsMouseOverColor = Color.FromBgra(0xFFBBBBBB);
_frame = new Image
{
Brush = new VideoBrush(_videoPlayer),
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0.0f, 0.0f, 0.0f, playbackButtonsSize),
Parent = this,
};
var playbackButtonsArea = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Offsets = new Margin(0, 0, -playbackButtonsSize, playbackButtonsSize),
Parent = this
};
var playbackButtonsPanel = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
AnchorPreset = AnchorPresets.VerticalStretchCenter,
Offsets = Margin.Zero,
Parent = playbackButtonsArea,
};
_seekBegin = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline start (Home)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Rotation = 180.0f,
Parent = playbackButtonsPanel
};
_seekBegin.Clicked += (image, button) => SeekBegin();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekLeft = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame back (Left Arrow)",
Brush = new SpriteBrush(icons.Left32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekLeft.Clicked += (image, button) => SeekLeft();
playbackButtonsPanel.Width += playbackButtonsSize;
_stop = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Stop playback",
Brush = new SpriteBrush(icons.Stop64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_stop.Clicked += (image, button) => Stop();
playbackButtonsPanel.Width += playbackButtonsSize;
_playPause = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Play/pause playback (Space)",
Brush = new SpriteBrush(icons.Play64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_playPause.Clicked += (image, button) => PlayPause();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekRight = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame forward (Right Arrow)",
Brush = new SpriteBrush(icons.Right32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekRight.Clicked += (image, button) => SeekRight();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekEnd = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline end (End)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekEnd.Clicked += (image, button) => SeekEnd();
playbackButtonsPanel.Width += playbackButtonsSize;
playbackButtonsPanel.X = (playbackButtonsPanel.Parent.Width - playbackButtonsPanel.Width) * 0.5f;
}
private void PlayPause()
{
if (_videoPlayer.State == VideoPlayer.States.Playing)
_videoPlayer.Pause();
else
_videoPlayer.Play();
}
private void Stop()
{
_videoPlayer.Stop();
}
private void SeekBegin()
{
_videoPlayer.Time = 0.0f;
}
private void SeekEnd()
{
_videoPlayer.Time = _videoPlayer.Duration;
}
private void SeekLeft()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time -= 1.0f / _videoPlayer.FrameRate;
}
private void SeekRight()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time += 1.0f / _videoPlayer.FrameRate;
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.ArrowLeft:
SeekLeft();
return true;
case KeyboardKeys.ArrowRight:
SeekRight();
return true;
case KeyboardKeys.Home:
SeekBegin();
return true;
case KeyboardKeys.End:
SeekEnd();
return true;
case KeyboardKeys.Spacebar:
PlayPause();
return true;
}
return false;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
// Update UI
var state = _videoPlayer.State;
var icons = Editor.Icons;
_stop.Enabled = state != VideoPlayer.States.Stopped;
_seekLeft.Enabled = _seekRight.Enabled = state != VideoPlayer.States.Playing;
((SpriteBrush)_playPause.Brush).Sprite = state == VideoPlayer.States.Playing ? icons.Pause64 : icons.Play64;
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
_videoPlayer.Stop();
Object.Destroy(ref _videoPlayer);
_item.RemoveReference(this);
_item = null;
base.OnDestroy();
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
if (item == _item)
Close();
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
if (item == _item)
Close();
}
}
}

View File

@@ -311,6 +311,23 @@ namespace FlaxEditor.Windows
{ {
if (selection[i] is BinaryAssetItem binaryAssetItem) if (selection[i] is BinaryAssetItem binaryAssetItem)
Editor.ContentImporting.Reimport(binaryAssetItem); Editor.ContentImporting.Reimport(binaryAssetItem);
else if (selection[i] is PrefabItem prefabItem)
{
var prefab = FlaxEngine.Content.Load<Prefab>(prefabItem.ID);
var modelPrefab = prefab.GetDefaultInstance().GetScript<ModelPrefab>();
if (!modelPrefab)
continue;
var importPath = modelPrefab.ImportPath;
var editor = Editor.Instance;
if (editor.ContentImporting.GetReimportPath("Model Prefab", ref importPath))
continue;
var folder = editor.ContentDatabase.Find(Path.GetDirectoryName(prefab.Path)) as ContentFolder;
if (folder == null)
continue;
var importOptions = modelPrefab.ImportOptions;
importOptions.Type = FlaxEngine.Tools.ModelTool.ModelType.Prefab;
editor.ContentImporting.Import(importPath, folder, true, importOptions);
}
} }
} }

View File

@@ -767,13 +767,6 @@ namespace FlaxEditor.Windows
Platform = BuildPlatform.Windows64, Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development, Mode = BuildConfiguration.Development,
}, },
new BuildTarget
{
Name = "Windows 32bit",
Output = "Output\\Win32",
Platform = BuildPlatform.Windows32,
Mode = BuildConfiguration.Development,
},
} }
}; };
_data = presets; _data = presets;
@@ -793,9 +786,9 @@ namespace FlaxEditor.Windows
Array.Copy(_data[_selectedPresetIndex].Targets, targets, count); Array.Copy(_data[_selectedPresetIndex].Targets, targets, count);
targets[count] = new BuildTarget targets[count] = new BuildTarget
{ {
Name = "Xbox One", Name = "Windows 64bit",
Output = "Output\\XboxOne", Output = "Output\\Win64",
Platform = BuildPlatform.XboxOne, Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development, Mode = BuildConfiguration.Development,
}; };
_data[_selectedPresetIndex].Targets = targets; _data[_selectedPresetIndex].Targets = targets;

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEngine; using FlaxEngine;
@@ -96,14 +95,6 @@ namespace FlaxEditor.Windows
set => Graphics.ShadowMapsQuality = value; set => Graphics.ShadowMapsQuality = value;
} }
[DefaultValue(false)]
[EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")]
public bool AllowCSMBlending
{
get => Graphics.AllowCSMBlending;
set => Graphics.AllowCSMBlending = value;
}
[NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)] [NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)]
[EditorOrder(1400), EditorDisplay("Quality")] [EditorOrder(1400), EditorDisplay("Quality")]
[Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")] [Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")]

View File

@@ -232,12 +232,12 @@ void BehaviorTree::OnScriptsReloadEnd()
Graph.Setup(this); Graph.Setup(this);
} }
void BehaviorTree::GetReferences(Array<Guid>& output) const void BehaviorTree::GetReferences(Array<Guid>& assets, Array<String>& files) const
{ {
// Base // Base
BinaryAsset::GetReferences(output); BinaryAsset::GetReferences(assets, files);
Graph.GetReferences(output); Graph.GetReferences(assets);
// Extract refs from serialized nodes data // Extract refs from serialized nodes data
for (const BehaviorTreeGraphNode& n : Graph.Nodes) for (const BehaviorTreeGraphNode& n : Graph.Nodes)
@@ -246,7 +246,7 @@ void BehaviorTree::GetReferences(Array<Guid>& output) const
continue; continue;
const Variant& data = n.Values[1]; const Variant& data = n.Values[1];
if (data.Type == VariantType::Blob) if (data.Type == VariantType::Blob)
JsonAssetBase::GetReferences(StringAnsiView((char*)data.AsBlob.Data, data.AsBlob.Length), output); JsonAssetBase::GetReferences(StringAnsiView((char*)data.AsBlob.Data, data.AsBlob.Length), assets);
} }
} }

View File

@@ -98,7 +98,7 @@ public:
// [BinaryAsset] // [BinaryAsset]
void OnScriptingDispose() override; void OnScriptingDispose() override;
#if USE_EDITOR #if USE_EDITOR
void GetReferences(Array<Guid>& output) const override; void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif #endif
protected: protected:

View File

@@ -75,16 +75,16 @@ bool SceneAnimation::SaveTimeline(const BytesContainer& data)
#if USE_EDITOR #if USE_EDITOR
void SceneAnimation::GetReferences(Array<Guid>& output) const void SceneAnimation::GetReferences(Array<Guid>& assets, Array<String>& files) const
{ {
// Base // Base
BinaryAsset::GetReferences(output); BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < Tracks.Count(); i++) for (int32 i = 0; i < Tracks.Count(); i++)
{ {
const auto& track = Tracks[i]; const auto& track = Tracks[i];
if (track.Asset) if (track.Asset)
output.Add(track.Asset->GetID()); assets.Add(track.Asset->GetID());
} }
} }

View File

@@ -464,7 +464,7 @@ public:
public: public:
// [BinaryAsset] // [BinaryAsset]
#if USE_EDITOR #if USE_EDITOR
void GetReferences(Array<Guid>& output) const override; void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif #endif
protected: protected:

View File

@@ -148,44 +148,6 @@ void Audio::SetEnableHRTF(bool value)
AudioBackend::Listener::ReinitializeAll(); AudioBackend::Listener::ReinitializeAll();
} }
void Audio::OnAddListener(AudioListener* listener)
{
ASSERT(!Listeners.Contains(listener));
if (Listeners.Count() >= AUDIO_MAX_LISTENERS)
{
LOG(Error, "Unsupported amount of the audio listeners!");
return;
}
Listeners.Add(listener);
AudioBackend::Listener::OnAdd(listener);
}
void Audio::OnRemoveListener(AudioListener* listener)
{
if (!Listeners.Remove(listener))
{
AudioBackend::Listener::OnRemove(listener);
}
}
void Audio::OnAddSource(AudioSource* source)
{
ASSERT(!Sources.Contains(source));
Sources.Add(source);
AudioBackend::Source::OnAdd(source);
}
void Audio::OnRemoveSource(AudioSource* source)
{
if (!Sources.Remove(source))
{
AudioBackend::Source::OnRemove(source);
}
}
bool AudioService::Init() bool AudioService::Init()
{ {
PROFILE_CPU_NAMED("Audio.Init"); PROFILE_CPU_NAMED("Audio.Init");

View File

@@ -97,11 +97,4 @@ public:
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
API_PROPERTY() static void SetEnableHRTF(bool value); API_PROPERTY() static void SetEnableHRTF(bool value);
public:
static void OnAddListener(AudioListener* listener);
static void OnRemoveListener(AudioListener* listener);
static void OnAddSource(AudioSource* source);
static void OnRemoveSource(AudioSource* source);
}; };

View File

@@ -15,7 +15,6 @@ class AudioBackend
friend class AudioService; friend class AudioService;
public: public:
enum class FeatureFlags enum class FeatureFlags
{ {
None = 0, None = 0,
@@ -26,41 +25,37 @@ public:
static AudioBackend* Instance; static AudioBackend* Instance;
private: private:
// Listener // Listener
virtual void Listener_OnAdd(AudioListener* listener) = 0; virtual void Listener_Reset() = 0;
virtual void Listener_OnRemove(AudioListener* listener) = 0; virtual void Listener_VelocityChanged(const Vector3& velocity) = 0;
virtual void Listener_VelocityChanged(AudioListener* listener) = 0; virtual void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) = 0;
virtual void Listener_TransformChanged(AudioListener* listener) = 0;
virtual void Listener_ReinitializeAll() = 0; virtual void Listener_ReinitializeAll() = 0;
// Source // Source
virtual void Source_OnAdd(AudioSource* source) = 0; virtual uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) = 0;
virtual void Source_OnRemove(AudioSource* source) = 0; virtual void Source_Remove(uint32 sourceID) = 0;
virtual void Source_VelocityChanged(AudioSource* source) = 0; virtual void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) = 0;
virtual void Source_TransformChanged(AudioSource* source) = 0; virtual void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) = 0;
virtual void Source_VolumeChanged(AudioSource* source) = 0; virtual void Source_VolumeChanged(uint32 sourceID, float volume) = 0;
virtual void Source_PitchChanged(AudioSource* source) = 0; virtual void Source_PitchChanged(uint32 sourceID, float pitch) = 0;
virtual void Source_PanChanged(AudioSource* source) = 0; virtual void Source_PanChanged(uint32 sourceID, float pan) = 0;
virtual void Source_IsLoopingChanged(AudioSource* source) = 0; virtual void Source_IsLoopingChanged(uint32 sourceID, bool loop) = 0;
virtual void Source_SpatialSetupChanged(AudioSource* source) = 0; virtual void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) = 0;
virtual void Source_ClipLoaded(AudioSource* source) = 0; virtual void Source_Play(uint32 sourceID) = 0;
virtual void Source_Cleanup(AudioSource* source) = 0; virtual void Source_Pause(uint32 sourceID) = 0;
virtual void Source_Play(AudioSource* source) = 0; virtual void Source_Stop(uint32 sourceID) = 0;
virtual void Source_Pause(AudioSource* source) = 0; virtual void Source_SetCurrentBufferTime(uint32 sourceID, float value) = 0;
virtual void Source_Stop(AudioSource* source) = 0; virtual float Source_GetCurrentBufferTime(uint32 id) = 0;
virtual void Source_SetCurrentBufferTime(AudioSource* source, float value) = 0; virtual void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) = 0;
virtual float Source_GetCurrentBufferTime(const AudioSource* source) = 0; virtual void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) = 0;
virtual void Source_SetNonStreamingBuffer(AudioSource* source) = 0; virtual void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) = 0;
virtual void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) = 0; virtual void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) = 0;
virtual void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) = 0; virtual void Source_DequeueProcessedBuffers(uint32 sourceID) = 0;
virtual void Source_QueueBuffer(AudioSource* source, uint32 bufferId) = 0;
virtual void Source_DequeueProcessedBuffers(AudioSource* source) = 0;
// Buffer // Buffer
virtual uint32 Buffer_Create() = 0; virtual uint32 Buffer_Create() = 0;
virtual void Buffer_Delete(uint32 bufferId) = 0; virtual void Buffer_Delete(uint32 bufferID) = 0;
virtual void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) = 0; virtual void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) = 0;
// Base // Base
virtual const Char* Base_Name() = 0; virtual const Char* Base_Name() = 0;
@@ -73,35 +68,27 @@ private:
virtual void Base_Dispose() = 0; virtual void Base_Dispose() = 0;
public: public:
virtual ~AudioBackend() virtual ~AudioBackend()
{ {
} }
public: public:
class Listener class Listener
{ {
public: public:
FORCE_INLINE static void Reset()
FORCE_INLINE static void OnAdd(AudioListener* listener)
{ {
Instance->Listener_OnAdd(listener); Instance->Listener_Reset();
} }
FORCE_INLINE static void OnRemove(AudioListener* listener) FORCE_INLINE static void VelocityChanged(const Vector3& velocity)
{ {
Instance->Listener_OnRemove(listener); Instance->Listener_VelocityChanged(velocity);
} }
FORCE_INLINE static void VelocityChanged(AudioListener* listener) FORCE_INLINE static void TransformChanged(const Vector3& position, const Quaternion& orientation)
{ {
Instance->Listener_VelocityChanged(listener); Instance->Listener_TransformChanged(position, orientation);
}
FORCE_INLINE static void TransformChanged(AudioListener* listener)
{
Instance->Listener_TransformChanged(listener);
} }
FORCE_INLINE static void ReinitializeAll() FORCE_INLINE static void ReinitializeAll()
@@ -113,130 +100,118 @@ public:
class Source class Source
{ {
public: public:
FORCE_INLINE static uint32 Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
FORCE_INLINE static void OnAdd(AudioSource* source)
{ {
Instance->Source_OnAdd(source); return Instance->Source_Add(format, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
} }
FORCE_INLINE static void OnRemove(AudioSource* source) FORCE_INLINE static void Remove(uint32 sourceID)
{ {
Instance->Source_OnRemove(source); Instance->Source_Remove(sourceID);
} }
FORCE_INLINE static void VelocityChanged(AudioSource* source) FORCE_INLINE static void VelocityChanged(uint32 sourceID, const Vector3& velocity)
{ {
Instance->Source_VelocityChanged(source); Instance->Source_VelocityChanged(sourceID, velocity);
} }
FORCE_INLINE static void TransformChanged(AudioSource* source) FORCE_INLINE static void TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{ {
Instance->Source_TransformChanged(source); Instance->Source_TransformChanged(sourceID, position, orientation);
} }
FORCE_INLINE static void VolumeChanged(AudioSource* source) FORCE_INLINE static void VolumeChanged(uint32 sourceID, float volume)
{ {
Instance->Source_VolumeChanged(source); Instance->Source_VolumeChanged(sourceID, volume);
} }
FORCE_INLINE static void PitchChanged(AudioSource* source) FORCE_INLINE static void PitchChanged(uint32 sourceID, float pitch)
{ {
Instance->Source_PitchChanged(source); Instance->Source_PitchChanged(sourceID, pitch);
} }
FORCE_INLINE static void PanChanged(AudioSource* source) FORCE_INLINE static void PanChanged(uint32 sourceID, float pan)
{ {
Instance->Source_PanChanged(source); Instance->Source_PanChanged(sourceID, pan);
} }
FORCE_INLINE static void IsLoopingChanged(AudioSource* source) FORCE_INLINE static void IsLoopingChanged(uint32 sourceID, bool loop)
{ {
Instance->Source_IsLoopingChanged(source); Instance->Source_IsLoopingChanged(sourceID, loop);
} }
FORCE_INLINE static void SpatialSetupChanged(AudioSource* source) FORCE_INLINE static void SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
Instance->Source_SpatialSetupChanged(source); Instance->Source_SpatialSetupChanged(sourceID, spatial, attenuation, minDistance, doppler);
} }
FORCE_INLINE static void ClipLoaded(AudioSource* source) FORCE_INLINE static void Play(uint32 sourceID)
{ {
Instance->Source_ClipLoaded(source); Instance->Source_Play(sourceID);
} }
FORCE_INLINE static void Cleanup(AudioSource* source) FORCE_INLINE static void Pause(uint32 sourceID)
{ {
Instance->Source_Cleanup(source); Instance->Source_Pause(sourceID);
} }
FORCE_INLINE static void Play(AudioSource* source) FORCE_INLINE static void Stop(uint32 sourceID)
{ {
Instance->Source_Play(source); Instance->Source_Stop(sourceID);
} }
FORCE_INLINE static void Pause(AudioSource* source) FORCE_INLINE static void SetCurrentBufferTime(uint32 sourceID, float value)
{ {
Instance->Source_Pause(source); Instance->Source_SetCurrentBufferTime(sourceID, value);
} }
FORCE_INLINE static void Stop(AudioSource* source) FORCE_INLINE static float GetCurrentBufferTime(uint32 sourceID)
{ {
Instance->Source_Stop(source); return Instance->Source_GetCurrentBufferTime(sourceID);
} }
FORCE_INLINE static void SetCurrentBufferTime(AudioSource* source, float value) FORCE_INLINE static void SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{ {
Instance->Source_SetCurrentBufferTime(source, value); Instance->Source_SetNonStreamingBuffer(sourceID, bufferID);
} }
FORCE_INLINE static float GetCurrentBufferTime(const AudioSource* source) FORCE_INLINE static void GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{ {
return Instance->Source_GetCurrentBufferTime(source); Instance->Source_GetProcessedBuffersCount(sourceID, processedBuffersCount);
} }
FORCE_INLINE static void SetNonStreamingBuffer(AudioSource* source) FORCE_INLINE static void GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{ {
Instance->Source_SetNonStreamingBuffer(source); Instance->Source_GetQueuedBuffersCount(sourceID, queuedBuffersCount);
} }
FORCE_INLINE static void GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) FORCE_INLINE static void QueueBuffer(uint32 sourceID, uint32 bufferID)
{ {
Instance->Source_GetProcessedBuffersCount(source, processedBuffersCount); Instance->Source_QueueBuffer(sourceID, bufferID);
} }
FORCE_INLINE static void GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) FORCE_INLINE static void DequeueProcessedBuffers(uint32 sourceID)
{ {
Instance->Source_GetQueuedBuffersCount(source, queuedBuffersCount); Instance->Source_DequeueProcessedBuffers(sourceID);
}
FORCE_INLINE static void QueueBuffer(AudioSource* source, uint32 bufferId)
{
Instance->Source_QueueBuffer(source, bufferId);
}
FORCE_INLINE static void DequeueProcessedBuffers(AudioSource* source)
{
Instance->Source_DequeueProcessedBuffers(source);
} }
}; };
class Buffer class Buffer
{ {
public: public:
FORCE_INLINE static uint32 Create() FORCE_INLINE static uint32 Create()
{ {
return Instance->Buffer_Create(); return Instance->Buffer_Create();
} }
FORCE_INLINE static void Delete(uint32 bufferId) FORCE_INLINE static void Delete(uint32 bufferID)
{ {
Instance->Buffer_Delete(bufferId); Instance->Buffer_Delete(bufferID);
} }
FORCE_INLINE static void Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) FORCE_INLINE static void Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{ {
Instance->Buffer_Write(bufferId, samples, info); Instance->Buffer_Write(bufferID, samples, info);
} }
}; };

View File

@@ -21,6 +21,13 @@ public:
Vector3 Velocity; Vector3 Velocity;
Vector3 Position; Vector3 Position;
Quaternion Orientation; Quaternion Orientation;
void Reset()
{
Velocity = Vector3::Zero;
Position = Vector3::Zero;
Orientation = Quaternion::Identity;
}
}; };
struct Source struct Source
@@ -153,18 +160,8 @@ public:
break; break;
case 2: case 2:
default: // TODO: implement multi-channel support (eg. 5.1, 7.1) default: // TODO: implement multi-channel support (eg. 5.1, 7.1)
if (sourceChannels == 1) outputMatrix[0] = channels[FrontLeft];
{ outputMatrix[sourceChannels + 1] = channels[FrontRight];
outputMatrix[0] = channels[FrontLeft];
outputMatrix[1] = channels[FrontRight];
}
else if (sourceChannels == 2)
{
outputMatrix[0] = channels[FrontLeft];
outputMatrix[1] = 0.0f;
outputMatrix[2] = 0.0f;
outputMatrix[3] = channels[FrontRight];
}
break; break;
} }
} }

View File

@@ -31,16 +31,16 @@ bool AudioClip::StreamingTask::Run()
for (int32 i = 0; i < queue.Count(); i++) for (int32 i = 0; i < queue.Count(); i++)
{ {
const auto idx = queue[i]; const auto idx = queue[i];
uint32& bufferId = clip->Buffers[idx]; uint32& bufferID = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID) if (bufferID == 0)
{ {
bufferId = AudioBackend::Buffer::Create(); bufferID = AudioBackend::Buffer::Create();
} }
else else
{ {
// Release unused data // Release unused data
AudioBackend::Buffer::Delete(bufferId); AudioBackend::Buffer::Delete(bufferID);
bufferId = AUDIO_BUFFER_ID_INVALID; bufferID = 0;
} }
} }
@@ -267,7 +267,7 @@ Task* AudioClip::CreateStreamingTask(int32 residency)
for (int32 i = 0; i < StreamingQueue.Count(); i++) for (int32 i = 0; i < StreamingQueue.Count(); i++)
{ {
const int32 idx = StreamingQueue[i]; const int32 idx = StreamingQueue[i];
if (Buffers[idx] == AUDIO_BUFFER_ID_INVALID) if (Buffers[idx] == 0)
{ {
const auto task = (Task*)RequestChunkDataAsync(idx); const auto task = (Task*)RequestChunkDataAsync(idx);
if (task) if (task)
@@ -383,8 +383,8 @@ Asset::LoadResult AudioClip::load()
void AudioClip::unload(bool isReloading) void AudioClip::unload(bool isReloading)
{ {
bool hasAnyBuffer = false; bool hasAnyBuffer = false;
for (const AUDIO_BUFFER_ID_TYPE bufferId : Buffers) for (const uint32 bufferID : Buffers)
hasAnyBuffer |= bufferId != AUDIO_BUFFER_ID_INVALID; hasAnyBuffer |= bufferID != 0;
// Stop any audio sources that are using this clip right now // Stop any audio sources that are using this clip right now
// TODO: find better way to collect audio sources using audio clip and impl it for AudioStreamingHandler too // TODO: find better way to collect audio sources using audio clip and impl it for AudioStreamingHandler too
@@ -399,10 +399,10 @@ void AudioClip::unload(bool isReloading)
StreamingQueue.Clear(); StreamingQueue.Clear();
if (hasAnyBuffer && AudioBackend::Instance) if (hasAnyBuffer && AudioBackend::Instance)
{ {
for (AUDIO_BUFFER_ID_TYPE bufferId : Buffers) for (uint32 bufferID : Buffers)
{ {
if (bufferId != AUDIO_BUFFER_ID_INVALID) if (bufferID != 0)
AudioBackend::Buffer::Delete(bufferId); AudioBackend::Buffer::Delete(bufferID);
} }
} }
Buffers.Clear(); Buffers.Clear();
@@ -413,8 +413,8 @@ void AudioClip::unload(bool isReloading)
bool AudioClip::WriteBuffer(int32 chunkIndex) bool AudioClip::WriteBuffer(int32 chunkIndex)
{ {
// Ignore if buffer is not created // Ignore if buffer is not created
const uint32 bufferId = Buffers[chunkIndex]; const uint32 bufferID = Buffers[chunkIndex];
if (bufferId == AUDIO_BUFFER_ID_INVALID) if (bufferID == 0)
return false; return false;
// Ensure audio backend exists // Ensure audio backend exists
@@ -475,6 +475,6 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
} }
// Write samples to the audio buffer // Write samples to the audio buffer
AudioBackend::Buffer::Write(bufferId, data.Get(), info); AudioBackend::Buffer::Write(bufferID, data.Get(), info);
return false; return false;
} }

View File

@@ -88,7 +88,7 @@ public:
/// <summary> /// <summary>
/// The audio backend buffers (internal ids) collection used by this audio clip. /// The audio backend buffers (internal ids) collection used by this audio clip.
/// </summary> /// </summary>
Array<AUDIO_BUFFER_ID_TYPE, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> Buffers; Array<uint32, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> Buffers;
/// <summary> /// <summary>
/// The streaming cache. Contains indices of chunks to stream. If empty no streaming required. Managed by AudioStreamingHandler and used by the Audio streaming tasks. /// The streaming cache. Contains indices of chunks to stream. If empty no streaming required. Managed by AudioStreamingHandler and used by the Audio streaming tasks.

View File

@@ -3,12 +3,14 @@
#include "AudioListener.h" #include "AudioListener.h"
#include "Engine/Engine/Time.h" #include "Engine/Engine/Time.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#include "Engine/Core/Log.h"
#include "AudioBackend.h" #include "AudioBackend.h"
#include "Audio.h" #include "Audio.h"
AudioListener::AudioListener(const SpawnParams& params) AudioListener::AudioListener(const SpawnParams& params)
: Actor(params) : Actor(params)
, _velocity(Vector3::Zero) , _velocity(Vector3::Zero)
, _prevPos(Vector3::Zero)
{ {
} }
@@ -27,7 +29,7 @@ void AudioListener::Update()
_prevPos = pos; _prevPos = pos;
if (_velocity != prevVelocity) if (_velocity != prevVelocity)
{ {
AudioBackend::Listener::VelocityChanged(this); AudioBackend::Listener::VelocityChanged(_velocity);
} }
} }
@@ -35,9 +37,18 @@ void AudioListener::OnEnable()
{ {
_prevPos = GetPosition(); _prevPos = GetPosition();
_velocity = Vector3::Zero; _velocity = Vector3::Zero;
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
Audio::OnAddListener(this); {
GetScene()->Ticking.Update.AddTick<AudioListener, &AudioListener::Update>(this); LOG(Error, "Unsupported amount of the audio listeners!");
}
else
{
ASSERT(!Audio::Listeners.Contains(this));
Audio::Listeners.Add(this);
AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
GetScene()->Ticking.Update.AddTick<AudioListener, &AudioListener::Update>(this);
}
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this); GetSceneRendering()->AddViewportIcon(this);
#endif #endif
@@ -51,8 +62,11 @@ void AudioListener::OnDisable()
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this); GetSceneRendering()->RemoveViewportIcon(this);
#endif #endif
GetScene()->Ticking.Update.RemoveTick(this); if (!Audio::Listeners.Remove(this))
Audio::OnRemoveListener(this); {
GetScene()->Ticking.Update.RemoveTick(this);
AudioBackend::Listener::Reset();
}
// Base // Base
Actor::OnDisable(); Actor::OnDisable();
@@ -68,6 +82,6 @@ void AudioListener::OnTransformChanged()
if (IsActiveInHierarchy() && IsDuringPlay()) if (IsActiveInHierarchy() && IsDuringPlay())
{ {
AudioBackend::Listener::TransformChanged(this); AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
} }
} }

View File

@@ -32,8 +32,8 @@ void AudioSource::SetVolume(float value)
if (Math::NearEqual(_volume, value)) if (Math::NearEqual(_volume, value))
return; return;
_volume = value; _volume = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::VolumeChanged(this); AudioBackend::Source::VolumeChanged(SourceID, _volume);
} }
void AudioSource::SetPitch(float value) void AudioSource::SetPitch(float value)
@@ -42,8 +42,8 @@ void AudioSource::SetPitch(float value)
if (Math::NearEqual(_pitch, value)) if (Math::NearEqual(_pitch, value))
return; return;
_pitch = value; _pitch = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::PitchChanged(this); AudioBackend::Source::PitchChanged(SourceID, _pitch);
} }
void AudioSource::SetPan(float value) void AudioSource::SetPan(float value)
@@ -52,8 +52,8 @@ void AudioSource::SetPan(float value)
if (Math::NearEqual(_pan, value)) if (Math::NearEqual(_pan, value))
return; return;
_pan = value; _pan = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::PanChanged(this); AudioBackend::Source::PanChanged(SourceID, _pan);
} }
void AudioSource::SetIsLooping(bool value) void AudioSource::SetIsLooping(bool value)
@@ -63,8 +63,8 @@ void AudioSource::SetIsLooping(bool value)
_loop = value; _loop = value;
// When streaming we handle looping manually by the proper buffers submission // When streaming we handle looping manually by the proper buffers submission
if (SourceIDs.HasItems() && !UseStreaming()) if (SourceID && !UseStreaming())
AudioBackend::Source::IsLoopingChanged(this); AudioBackend::Source::IsLoopingChanged(SourceID, _loop);
} }
void AudioSource::SetPlayOnStart(bool value) void AudioSource::SetPlayOnStart(bool value)
@@ -83,8 +83,8 @@ void AudioSource::SetMinDistance(float value)
if (Math::NearEqual(_minDistance, value)) if (Math::NearEqual(_minDistance, value))
return; return;
_minDistance = value; _minDistance = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::SpatialSetupChanged(this); AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
} }
void AudioSource::SetAttenuation(float value) void AudioSource::SetAttenuation(float value)
@@ -93,8 +93,8 @@ void AudioSource::SetAttenuation(float value)
if (Math::NearEqual(_attenuation, value)) if (Math::NearEqual(_attenuation, value))
return; return;
_attenuation = value; _attenuation = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::SpatialSetupChanged(this); AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
} }
void AudioSource::SetDopplerFactor(float value) void AudioSource::SetDopplerFactor(float value)
@@ -103,8 +103,8 @@ void AudioSource::SetDopplerFactor(float value)
if (Math::NearEqual(_dopplerFactor, value)) if (Math::NearEqual(_dopplerFactor, value))
return; return;
_dopplerFactor = value; _dopplerFactor = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::SpatialSetupChanged(this); AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
} }
void AudioSource::SetAllowSpatialization(bool value) void AudioSource::SetAllowSpatialization(bool value)
@@ -112,8 +112,8 @@ void AudioSource::SetAllowSpatialization(bool value)
if (_allowSpatialization == value) if (_allowSpatialization == value)
return; return;
_allowSpatialization = value; _allowSpatialization = value;
if (SourceIDs.HasItems()) if (SourceID)
AudioBackend::Source::SpatialSetupChanged(this); AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
} }
void AudioSource::Play() void AudioSource::Play()
@@ -121,19 +121,26 @@ void AudioSource::Play()
auto state = _state; auto state = _state;
if (state == States::Playing) if (state == States::Playing)
return; return;
if (Clip == nullptr) if (Clip == nullptr || Clip->WaitForLoaded())
{ {
LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath()); LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath());
return; return;
} }
if (SourceID == 0)
{
// Create audio source
SourceID = AudioBackend::Source::Add(Clip->Info(), GetPosition(), GetOrientation(), GetVolume(), GetPitch(), GetPan(), GetIsLooping() && !UseStreaming(), Is3D(), GetAttenuation(), GetMinDistance(), GetDopplerFactor());
if (SourceID == 0)
{
LOG(Warning, "Cannot create audio source ({0})", GetNamePath());
return;
}
}
_state = States::Playing; _state = States::Playing;
_isActuallyPlayingSth = false; _isActuallyPlayingSth = false;
// Don't block scripting if audio is not loaded or has missing streaming data
if (!Clip->IsLoaded())
return;
// Audio clips with disabled streaming are controlled by audio source, otherwise streaming manager will play it // Audio clips with disabled streaming are controlled by audio source, otherwise streaming manager will play it
if (Clip->IsStreamable()) if (Clip->IsStreamable())
{ {
@@ -152,10 +159,10 @@ void AudioSource::Play()
RequestStreamingBuffersUpdate(); RequestStreamingBuffersUpdate();
} }
} }
else if (SourceIDs.HasItems()) else if (SourceID)
{ {
// Play it right away // Play it right away
SetNonStreamingBuffer(); AudioBackend::Source::SetNonStreamingBuffer(SourceID, Clip->Buffers[0]);
PlayInternal(); PlayInternal();
} }
else else
@@ -171,10 +178,9 @@ void AudioSource::Pause()
return; return;
_state = States::Paused; _state = States::Paused;
if (_isActuallyPlayingSth) if (_isActuallyPlayingSth)
{ {
AudioBackend::Source::Pause(this); AudioBackend::Source::Pause(SourceID);
_isActuallyPlayingSth = false; _isActuallyPlayingSth = false;
} }
} }
@@ -187,23 +193,22 @@ void AudioSource::Stop()
_state = States::Stopped; _state = States::Stopped;
_isActuallyPlayingSth = false; _isActuallyPlayingSth = false;
_streamingFirstChunk = 0; _streamingFirstChunk = 0;
if (SourceID)
if (SourceIDs.HasItems()) AudioBackend::Source::Stop(SourceID);
AudioBackend::Source::Stop(this);
} }
float AudioSource::GetTime() const float AudioSource::GetTime() const
{ {
if (_state == States::Stopped || SourceIDs.IsEmpty() || !Clip->IsLoaded()) if (_state == States::Stopped || SourceID == 0 || !Clip->IsLoaded())
return 0.0f; return 0.0f;
float time = AudioBackend::Source::GetCurrentBufferTime(this); float time = AudioBackend::Source::GetCurrentBufferTime(SourceID);
if (UseStreaming()) if (UseStreaming())
{ {
// Apply time offset to the first streaming buffer binded to the source including the already queued buffers // Apply time offset to the first streaming buffer binded to the source including the already queued buffers
int32 numProcessedBuffers = 0; int32 numProcessedBuffers = 0;
AudioBackend::Source::GetProcessedBuffersCount(const_cast<AudioSource*>(this), numProcessedBuffers); AudioBackend::Source::GetProcessedBuffersCount(SourceID, numProcessedBuffers);
time += Clip->GetBufferStartTime(_streamingFirstChunk + numProcessedBuffers); time += Clip->GetBufferStartTime(_streamingFirstChunk + numProcessedBuffers);
} }
@@ -235,7 +240,7 @@ void AudioSource::SetTime(float time)
time = relativeTime; time = relativeTime;
} }
AudioBackend::Source::SetCurrentBufferTime(this, time); AudioBackend::Source::SetCurrentBufferTime(SourceID, time);
// Restore state if was stopped // Restore state if was stopped
if (isActuallyPlayingSth) if (isActuallyPlayingSth)
@@ -259,31 +264,29 @@ void AudioSource::RequestStreamingBuffersUpdate()
_needToUpdateStreamingBuffers = true; _needToUpdateStreamingBuffers = true;
} }
void AudioSource::Cleanup()
{
_savedState = GetState();
_savedTime = GetTime();
Stop();
if (SourceIDs.HasItems())
{
AudioBackend::Source::Cleanup(this);
SourceIDs.Clear();
}
}
void AudioSource::OnClipChanged() void AudioSource::OnClipChanged()
{ {
Stop(); Stop();
_clipChanged = true;
// Destroy current source (will be created on the next play), because clip might use different spatial options or audio data format
if (SourceID)
{
AudioBackend::Source::Remove(SourceID);
SourceID = 0;
}
} }
void AudioSource::OnClipLoaded() void AudioSource::OnClipLoaded()
{ {
AudioBackend::Source::ClipLoaded(this); if (!SourceID)
return;
// Reset spatial and playback
AudioBackend::Source::IsLoopingChanged(SourceID, _loop && !UseStreaming());
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
// Start playing if source was waiting for the clip to load // Start playing if source was waiting for the clip to load
if (SourceIDs.HasItems() && _state == States::Playing && !_isActuallyPlayingSth) if (_state == States::Playing && !_isActuallyPlayingSth)
{ {
if (Clip->IsStreamable()) if (Clip->IsStreamable())
{ {
@@ -293,7 +296,7 @@ void AudioSource::OnClipLoaded()
else else
{ {
// Play it right away // Play it right away
SetNonStreamingBuffer(); AudioBackend::Source::SetNonStreamingBuffer(SourceID, Clip->Buffers[0]);
PlayInternal(); PlayInternal();
} }
} }
@@ -301,42 +304,14 @@ void AudioSource::OnClipLoaded()
bool AudioSource::UseStreaming() const bool AudioSource::UseStreaming() const
{ {
return Clip && Clip->IsLoaded() && Clip->IsStreamable(); if (Clip == nullptr || Clip->WaitForLoaded())
} return false;
return Clip->IsStreamable();
void AudioSource::Restore()
{
if (Clip)
{
if (_savedState != States::Stopped)
Play();
if (_savedState == States::Paused)
Pause();
SetTime(_savedTime);
if (_savedState != States::Stopped && UseStreaming())
RequestStreamingBuffersUpdate();
}
}
void AudioSource::SetNonStreamingBuffer()
{
ASSERT(Clip && !Clip->IsStreamable());
AudioBackend::Source::SetNonStreamingBuffer(this);
} }
void AudioSource::PlayInternal() void AudioSource::PlayInternal()
{ {
if (_clipChanged && SourceIDs.HasItems()) AudioBackend::Source::Play(SourceID);
{
// If clip was changed between source setup (OnEnable) and actual playback start then ensure to flush any runtime properties with the audio backend
_clipChanged = false;
AudioBackend::Source::SpatialSetupChanged(this);
}
AudioBackend::Source::Play(this);
_isActuallyPlayingSth = true; _isActuallyPlayingSth = true;
} }
@@ -414,33 +389,33 @@ void AudioSource::Update()
const auto prevVelocity = _velocity; const auto prevVelocity = _velocity;
_velocity = (pos - _prevPos) / dt; _velocity = (pos - _prevPos) / dt;
_prevPos = pos; _prevPos = pos;
if (_velocity != prevVelocity) if (_velocity != prevVelocity && Is3D())
{ {
AudioBackend::Source::VelocityChanged(this); AudioBackend::Source::VelocityChanged(SourceID, _velocity);
} }
// Skip other update logic if it's not valid streamable source // Skip other update logic if it's not valid streamable source
if (!UseStreaming() || SourceIDs.IsEmpty()) if (!UseStreaming() || SourceID == 0)
return; return;
auto clip = Clip.Get(); auto clip = Clip.Get();
clip->Locker.Lock(); clip->Locker.Lock();
// Handle streaming buffers queue submit (ensure that clip has loaded the first chunk with audio data) // Handle streaming buffers queue submit (ensure that clip has loaded the first chunk with audio data)
if (_needToUpdateStreamingBuffers && clip->Buffers[_streamingFirstChunk] != AUDIO_BUFFER_ID_INVALID) if (_needToUpdateStreamingBuffers && clip->Buffers[_streamingFirstChunk] != 0)
{ {
// Get buffers in a queue count // Get buffers in a queue count
int32 numQueuedBuffers; int32 numQueuedBuffers;
AudioBackend::Source::GetQueuedBuffersCount(this, numQueuedBuffers); AudioBackend::Source::GetQueuedBuffersCount(SourceID, numQueuedBuffers);
// Queue missing buffers // Queue missing buffers
uint32 bufferId; uint32 bufferID;
if (numQueuedBuffers < 1 && (bufferId = clip->Buffers[_streamingFirstChunk]) != AUDIO_BUFFER_ID_INVALID) if (numQueuedBuffers < 1 && (bufferID = clip->Buffers[_streamingFirstChunk]) != 0)
{ {
AudioBackend::Source::QueueBuffer(this, bufferId); AudioBackend::Source::QueueBuffer(SourceID, bufferID);
} }
if (numQueuedBuffers < 2 && _streamingFirstChunk + 1 < clip->Buffers.Count() && (bufferId = clip->Buffers[_streamingFirstChunk + 1]) != AUDIO_BUFFER_ID_INVALID) if (numQueuedBuffers < 2 && _streamingFirstChunk + 1 < clip->Buffers.Count() && (bufferID = clip->Buffers[_streamingFirstChunk + 1]) != 0)
{ {
AudioBackend::Source::QueueBuffer(this, bufferId); AudioBackend::Source::QueueBuffer(SourceID, bufferID);
} }
// Clear flag // Clear flag
@@ -458,13 +433,13 @@ void AudioSource::Update()
{ {
// Get the processed buffers count // Get the processed buffers count
int32 numProcessedBuffers = 0; int32 numProcessedBuffers = 0;
AudioBackend::Source::GetProcessedBuffersCount(this, numProcessedBuffers); AudioBackend::Source::GetProcessedBuffersCount(SourceID, numProcessedBuffers);
if (numProcessedBuffers > 0) if (numProcessedBuffers > 0)
{ {
ASSERT(numProcessedBuffers <= ASSET_FILE_DATA_CHUNKS); ASSERT(numProcessedBuffers <= ASSET_FILE_DATA_CHUNKS);
// Unbind processed buffers from the source // Unbind processed buffers from the source
AudioBackend::Source::DequeueProcessedBuffers(this); AudioBackend::Source::DequeueProcessedBuffers(SourceID);
// Move the chunk pointer (AudioStreamingHandler will request new chunks streaming) // Move the chunk pointer (AudioStreamingHandler will request new chunks streaming)
_streamingFirstChunk += numProcessedBuffers; _streamingFirstChunk += numProcessedBuffers;
@@ -501,27 +476,53 @@ void AudioSource::OnEnable()
{ {
_prevPos = GetPosition(); _prevPos = GetPosition();
_velocity = Vector3::Zero; _velocity = Vector3::Zero;
_clipChanged = false;
Audio::OnAddSource(this); // Add source
ASSERT_LOW_LAYER(!Audio::Sources.Contains(this));
Audio::Sources.Add(this);
GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this); GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this);
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this); GetSceneRendering()->AddViewportIcon(this);
#endif #endif
// Restore playback state
if (Clip)
{
if (_savedState != States::Stopped)
Play();
if (_savedState == States::Paused)
Pause();
SetTime(_savedTime);
if (_savedState != States::Stopped && UseStreaming())
RequestStreamingBuffersUpdate();
}
// Base // Base
Actor::OnEnable(); Actor::OnEnable();
} }
void AudioSource::OnDisable() void AudioSource::OnDisable()
{ {
// Cache playback state
_savedState = GetState();
_savedTime = GetTime();
// End playback
Stop(); Stop();
// Remove source
#if USE_EDITOR #if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this); GetSceneRendering()->RemoveViewportIcon(this);
#endif #endif
GetScene()->Ticking.Update.RemoveTick(this); GetScene()->Ticking.Update.RemoveTick(this);
Audio::OnRemoveSource(this); if (SourceID)
{
AudioBackend::Source::Remove(SourceID);
SourceID = 0;
}
Audio::Sources.Remove(this);
// Base // Base
Actor::OnDisable(); Actor::OnDisable();
@@ -535,9 +536,9 @@ void AudioSource::OnTransformChanged()
_box = BoundingBox(_transform.Translation); _box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f); _sphere = BoundingSphere(_transform.Translation, 0.0f);
if (IsActiveInHierarchy() && SourceIDs.HasItems()) if (IsActiveInHierarchy() && SourceID && Is3D())
{ {
AudioBackend::Source::TransformChanged(this); AudioBackend::Source::TransformChanged(SourceID, _transform.Translation, _transform.Orientation);
} }
} }

View File

@@ -5,10 +5,9 @@
#include "Engine/Level/Actor.h" #include "Engine/Level/Actor.h"
#include "Engine/Content/AssetReference.h" #include "Engine/Content/AssetReference.h"
#include "AudioClip.h" #include "AudioClip.h"
#include "Config.h"
/// <summary> /// <summary>
/// Represents a source for emitting audio. Audio can be played spatially (gun shot), or normally (music). Each audio source must have an AudioClip to play - back, and it can also have a position in the case of spatial(3D) audio. /// Represents a source for emitting audio. Audio can be played spatially (gun shot), or normally (music). Each audio source must have an AudioClip to play - back, and it can also have a position in the case of spatial (3D) audio.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity. /// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity.
@@ -19,6 +18,7 @@ class FLAXENGINE_API AudioSource : public Actor
DECLARE_SCENE_OBJECT(AudioSource); DECLARE_SCENE_OBJECT(AudioSource);
friend class AudioStreamingHandler; friend class AudioStreamingHandler;
friend class AudioClip; friend class AudioClip;
public: public:
/// <summary> /// <summary>
/// Valid states in which AudioSource can be in. /// Valid states in which AudioSource can be in.
@@ -54,7 +54,6 @@ private:
bool _playOnStart; bool _playOnStart;
float _startTime; float _startTime;
bool _allowSpatialization; bool _allowSpatialization;
bool _clipChanged = false;
bool _isActuallyPlayingSth = false; bool _isActuallyPlayingSth = false;
bool _needToUpdateStreamingBuffers = false; bool _needToUpdateStreamingBuffers = false;
@@ -66,9 +65,9 @@ private:
public: public:
/// <summary> /// <summary>
/// The internal IDs of this audio source used by the audio backend (unique ID per context/listener). /// The internal ID of this audio source used by the audio backend. Empty if 0.
/// </summary> /// </summary>
Array<AUDIO_SOURCE_ID_TYPE, FixedAllocation<AUDIO_MAX_LISTENERS>> SourceIDs; uint32 SourceID = 0;
/// <summary> /// <summary>
/// The audio clip asset used as a source of the sound. /// The audio clip asset used as a source of the sound.
@@ -141,7 +140,7 @@ public:
API_PROPERTY() void SetIsLooping(bool value); API_PROPERTY() void SetIsLooping(bool value);
/// <summary> /// <summary>
/// Determines whether the audio clip should auto play on level start. /// Determines whether the audio clip should autoplay on level start.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"Audio Source\", \"Play On Start\")") API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"Audio Source\", \"Play On Start\")")
FORCE_INLINE bool GetPlayOnStart() const FORCE_INLINE bool GetPlayOnStart() const
@@ -159,7 +158,7 @@ public:
} }
/// <summary> /// <summary>
/// Determines whether the audio clip should auto play on game start. /// Determines whether the audio clip should autoplay on game start.
/// </summary> /// </summary>
API_PROPERTY() void SetPlayOnStart(bool value); API_PROPERTY() void SetPlayOnStart(bool value);
@@ -211,7 +210,7 @@ public:
API_PROPERTY() void SetDopplerFactor(float value); API_PROPERTY() void SetDopplerFactor(float value);
/// <summary> /// <summary>
/// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound. At 0, no distance attenuation ever occurs. /// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound.
/// </summary> /// </summary>
API_PROPERTY(Attributes="EditorOrder(80), DefaultValue(true), EditorDisplay(\"Audio Source\")") API_PROPERTY(Attributes="EditorOrder(80), DefaultValue(true), EditorDisplay(\"Audio Source\")")
FORCE_INLINE bool GetAllowSpatialization() const FORCE_INLINE bool GetAllowSpatialization() const
@@ -260,7 +259,7 @@ public:
API_PROPERTY() void SetTime(float time); API_PROPERTY() void SetTime(float time);
/// <summary> /// <summary>
/// Returns true if the sound source is three dimensional (volume and pitch varies based on listener distance and velocity). /// Returns true if the sound source is three-dimensional (volume and pitch varies based on listener distance and velocity).
/// </summary> /// </summary>
API_PROPERTY() bool Is3D() const; API_PROPERTY() bool Is3D() const;
@@ -269,11 +268,6 @@ public:
/// </summary> /// </summary>
API_PROPERTY() bool UseStreaming() const; API_PROPERTY() bool UseStreaming() const;
/// <summary>
/// Restores the saved time position and resumes/pauses the playback based on the state before. Used to restore audio source state after data rebuild (eg. by audio backend).
/// </summary>
void Restore();
public: public:
/// <summary> /// <summary>
/// Determines whether this audio source started playing audio via audio backend. After audio play it may wait for audio clip data to be loaded or streamed. /// Determines whether this audio source started playing audio via audio backend. After audio play it may wait for audio clip data to be loaded or streamed.
@@ -288,20 +282,10 @@ public:
/// </summary> /// </summary>
void RequestStreamingBuffersUpdate(); void RequestStreamingBuffersUpdate();
/// <summary>
/// Cleanups the cached data. Called by the Audio manager.
/// </summary>
void Cleanup();
private: private:
void OnClipChanged(); void OnClipChanged();
void OnClipLoaded(); void OnClipLoaded();
/// <summary>
/// Sets the single buffer from the audio clip that is not using dynamic streaming
/// </summary>
void SetNonStreamingBuffer();
/// <summary> /// <summary>
/// Plays the audio source. Should have buffer(s) binded before. /// Plays the audio source. Should have buffer(s) binded before.
/// </summary> /// </summary>

View File

@@ -3,18 +3,10 @@
#pragma once #pragma once
#include "Engine/Core/Config.h" #include "Engine/Core/Config.h"
#include "Engine/Content/Config.h"
// The maximum amount of listeners used at once // The maximum amount of listeners used at once
#define AUDIO_MAX_LISTENERS 8 #define AUDIO_MAX_LISTENERS 1
// The maximum amount of audio emitter buffers // The maximum amount of audio emitter buffers
#define AUDIO_MAX_SOURCE_BUFFERS (ASSET_FILE_DATA_CHUNKS) #define AUDIO_MAX_SOURCE_BUFFERS (ASSET_FILE_DATA_CHUNKS)
// The type of the audio source IDs used to identify it (per listener)
#define AUDIO_SOURCE_ID_TYPE uint32
// The type of the audio buffer IDs used to identify it
#define AUDIO_BUFFER_ID_TYPE uint32
// The buffer ID that is invalid (unused)
#define AUDIO_BUFFER_ID_INVALID 0

View File

@@ -6,19 +6,15 @@
#include "Engine/Audio/Audio.h" #include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioSource.h" #include "Engine/Audio/AudioSource.h"
void AudioBackendNone::Listener_OnAdd(AudioListener* listener) void AudioBackendNone::Listener_Reset()
{ {
} }
void AudioBackendNone::Listener_OnRemove(AudioListener* listener) void AudioBackendNone::Listener_VelocityChanged(const Vector3& velocity)
{ {
} }
void AudioBackendNone::Listener_VelocityChanged(AudioListener* listener) void AudioBackendNone::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{
}
void AudioBackendNone::Listener_TransformChanged(AudioListener* listener)
{ {
} }
@@ -26,91 +22,82 @@ void AudioBackendNone::Listener_ReinitializeAll()
{ {
} }
void AudioBackendNone::Source_OnAdd(AudioSource* source) uint32 AudioBackendNone::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{ {
source->Restore(); return 1;
} }
void AudioBackendNone::Source_OnRemove(AudioSource* source) void AudioBackendNone::Source_Remove(uint32 sourceID)
{
source->Cleanup();
}
void AudioBackendNone::Source_VelocityChanged(AudioSource* source)
{ {
} }
void AudioBackendNone::Source_TransformChanged(AudioSource* source) void AudioBackendNone::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{ {
} }
void AudioBackendNone::Source_VolumeChanged(AudioSource* source) void AudioBackendNone::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{ {
} }
void AudioBackendNone::Source_PitchChanged(AudioSource* source) void AudioBackendNone::Source_VolumeChanged(uint32 sourceID, float volume)
{ {
} }
void AudioBackendNone::Source_PanChanged(AudioSource* source) void AudioBackendNone::Source_PitchChanged(uint32 sourceID, float pitch)
{ {
} }
void AudioBackendNone::Source_IsLoopingChanged(AudioSource* source) void AudioBackendNone::Source_PanChanged(uint32 sourceID, float pan)
{ {
} }
void AudioBackendNone::Source_SpatialSetupChanged(AudioSource* source) void AudioBackendNone::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{ {
} }
void AudioBackendNone::Source_ClipLoaded(AudioSource* source) void AudioBackendNone::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
} }
void AudioBackendNone::Source_Cleanup(AudioSource* source) void AudioBackendNone::Source_Play(uint32 sourceID)
{ {
} }
void AudioBackendNone::Source_Play(AudioSource* source) void AudioBackendNone::Source_Pause(uint32 sourceID)
{ {
} }
void AudioBackendNone::Source_Pause(AudioSource* source) void AudioBackendNone::Source_Stop(uint32 sourceID)
{ {
} }
void AudioBackendNone::Source_Stop(AudioSource* source) void AudioBackendNone::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{ {
} }
void AudioBackendNone::Source_SetCurrentBufferTime(AudioSource* source, float value) float AudioBackendNone::Source_GetCurrentBufferTime(uint32 sourceID)
{
}
float AudioBackendNone::Source_GetCurrentBufferTime(const AudioSource* source)
{ {
return 0.0f; return 0.0f;
} }
void AudioBackendNone::Source_SetNonStreamingBuffer(AudioSource* source) void AudioBackendNone::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{ {
} }
void AudioBackendNone::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) void AudioBackendNone::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{ {
processedBuffersCount = 0; processedBuffersCount = 0;
} }
void AudioBackendNone::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) void AudioBackendNone::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{ {
} }
void AudioBackendNone::Source_QueueBuffer(AudioSource* source, uint32 bufferId) void AudioBackendNone::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{ {
} }
void AudioBackendNone::Source_DequeueProcessedBuffers(AudioSource* source) void AudioBackendNone::Source_DequeueProcessedBuffers(uint32 sourceID)
{ {
} }
@@ -119,11 +106,11 @@ uint32 AudioBackendNone::Buffer_Create()
return 1; return 1;
} }
void AudioBackendNone::Buffer_Delete(uint32 bufferId) void AudioBackendNone::Buffer_Delete(uint32 bufferID)
{ {
} }
void AudioBackendNone::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) void AudioBackendNone::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{ {
} }

View File

@@ -12,37 +12,33 @@
class AudioBackendNone : public AudioBackend class AudioBackendNone : public AudioBackend
{ {
public: public:
// [AudioBackend] // [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override; void Listener_Reset() override;
void Listener_OnRemove(AudioListener* listener) override; void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_VelocityChanged(AudioListener* listener) override; void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override; void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override; uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_OnRemove(AudioSource* source) override; void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(AudioSource* source) override; void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(AudioSource* source) override; void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(AudioSource* source) override; void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(AudioSource* source) override; void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(AudioSource* source) override; void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(AudioSource* source) override; void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(AudioSource* source) override; void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_ClipLoaded(AudioSource* source) override; void Source_Play(uint32 sourceID) override;
void Source_Cleanup(AudioSource* source) override; void Source_Pause(uint32 sourceID) override;
void Source_Play(AudioSource* source) override; void Source_Stop(uint32 sourceID) override;
void Source_Pause(AudioSource* source) override; void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
void Source_Stop(AudioSource* source) override; float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override; void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override; void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override; void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override; void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override; void Source_DequeueProcessedBuffers(uint32 sourceID) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Buffer_Create() override; uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override; void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override; void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override; const Char* Base_Name() override;
FeatureFlags Base_Features() override; FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override; void Base_OnActiveDeviceChanged() override;

View File

@@ -5,7 +5,9 @@
#include "AudioBackendOAL.h" #include "AudioBackendOAL.h"
#include "Engine/Platform/StringUtils.h" #include "Engine/Platform/StringUtils.h"
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Tools/AudioTool/AudioTool.h" #include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Engine/Units.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Audio/Audio.h" #include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioListener.h" #include "Engine/Audio/AudioListener.h"
@@ -19,12 +21,9 @@
#include <OpenAL/alc.h> #include <OpenAL/alc.h>
#include <OpenAL/alext.h> #include <OpenAL/alext.h>
#define ALC_MULTIPLE_LISTENERS 0 #define FLAX_DST_TO_OAL(x) x * UNITS_TO_METERS_SCALE
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -UNITS_TO_METERS_SCALE), ((ALfloat)vec.Y * UNITS_TO_METERS_SCALE), ((ALfloat)vec.Z * UNITS_TO_METERS_SCALE)
#define FLAX_COORD_SCALE 0.01f // units are meters #define FLAX_VEL_TO_OAL(vec) ((ALfloat)vec.X * -(UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE)), ((ALfloat)vec.Y * (UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE)), ((ALfloat)vec.Z * (UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE))
#define FLAX_DST_TO_OAL(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -FLAX_COORD_SCALE), ((ALfloat)vec.Y * FLAX_COORD_SCALE), ((ALfloat)vec.Z * FLAX_COORD_SCALE)
#define FLAX_VEL_TO_OAL(vec) ((ALfloat)vec.X * -(FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE))
#if BUILD_RELEASE #if BUILD_RELEASE
#define ALC_CHECK_ERROR(method) #define ALC_CHECK_ERROR(method)
#else #else
@@ -39,155 +38,96 @@
} }
#endif #endif
#if ALC_MULTIPLE_LISTENERS
#define ALC_FOR_EACH_CONTEXT() \
for (int32 i = 0; i < Contexts.Count(); i++)
{ \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(Contexts[i]);
#define ALC_GET_DEFAULT_CONTEXT() \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(Contexts[0]);
#define ALC_GET_LISTENER_CONTEXT(listener) \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(ALC::GetContext(listener)));
#else
#define ALC_FOR_EACH_CONTEXT() { int32 i = 0;
#define ALC_GET_DEFAULT_CONTEXT()
#define ALC_GET_LISTENER_CONTEXT(listener)
#endif
namespace ALC namespace ALC
{ {
ALCdevice* Device = nullptr; ALCdevice* Device = nullptr;
Array<ALCcontext*, FixedAllocation<AUDIO_MAX_LISTENERS>> Contexts; ALCcontext* Context = nullptr;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None; AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
CriticalSection Locker;
Dictionary<uint32, AudioDataInfo> SourceIDtoFormat;
bool IsExtensionSupported(const char* extension) bool IsExtensionSupported(const char* extension)
{ {
if (Device == nullptr) if (Device == nullptr)
return false; return false;
const int32 length = StringUtils::Length(extension); const int32 length = StringUtils::Length(extension);
if ((length > 2) && (StringUtils::Compare(extension, "ALC", 3) == 0)) if ((length > 2) && (StringUtils::Compare(extension, "ALC", 3) == 0))
return alcIsExtensionPresent(Device, extension) != AL_FALSE; return alcIsExtensionPresent(Device, extension) != AL_FALSE;
return alIsExtensionPresent(extension) != AL_FALSE; return alIsExtensionPresent(extension) != AL_FALSE;
} }
ALCcontext* GetContext(const class AudioListener* listener) void ClearContext()
{ {
#if ALC_MULTIPLE_LISTENERS if (Context)
const auto& listeners = Audio::Listeners;
if (listeners.HasItems())
{ {
ASSERT(listeners.Count() == Contexts.Count()); alcMakeContextCurrent(nullptr);
alcDestroyContext(Context);
const int32 numContexts = Contexts.Count(); Context = nullptr;
ALC_FOR_EACH_CONTEXT()
{
if (listeners[i] == listener)
return Contexts[i];
}
} }
ASSERT(Contexts.HasItems());
#else
ASSERT(Contexts.Count() == 1);
#endif
return Contexts[0];
}
FORCE_INLINE const Array<ALCcontext*, FixedAllocation<AUDIO_MAX_LISTENERS>>& GetContexts()
{
return Contexts;
}
void ClearContexts()
{
alcMakeContextCurrent(nullptr);
for (ALCcontext* context : Contexts)
alcDestroyContext(context);
Contexts.Clear();
} }
namespace Listener namespace Listener
{ {
void Rebuild(AudioListener* listener) void Rebuild(const AudioListener* listener)
{ {
AudioBackend::Listener::TransformChanged(listener); AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(listener->GetPosition(), listener->GetOrientation());
const Float3 velocity = listener->GetVelocity(); AudioBackend::Listener::VelocityChanged(listener->GetVelocity());
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
} }
} }
namespace Source namespace Source
{ {
void Rebuild(AudioSource* source) void Rebuild(uint32& sourceID, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{ {
ASSERT(source->SourceIDs.IsEmpty()); ASSERT_LOW_LAYER(sourceID == 0);
const bool is3D = source->Is3D(); alGenSources(1, &sourceID);
const bool loop = source->GetIsLooping() && !source->UseStreaming(); ASSERT_LOW_LAYER(sourceID != 0);
ALC_FOR_EACH_CONTEXT() alSourcef(sourceID, AL_GAIN, volume);
uint32 sourceID = 0; alSourcef(sourceID, AL_PITCH, pitch);
alGenSources(1, &sourceID); alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
source->SourceIDs.Add(sourceID); alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial);
} alSourcei(sourceID, AL_BUFFER, 0);
if (spatial)
ALC_FOR_EACH_CONTEXT() {
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_GAIN, source->GetVolume());
alSourcef(sourceID, AL_PITCH, source->GetPitch());
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_BUFFER, 0);
if (is3D)
{
#ifdef AL_SOFT_source_spatialize #ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_DOPPLER_FACTOR, source->GetDopplerFactor());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = source->GetPan() * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif #endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero));
} }
else
// Restore state after Cleanup {
source->Restore(); alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
} }
} }
void RebuildContexts(bool isChangingDevice) struct AudioSourceState
{
AudioSource::States State;
float Time;
};
void RebuildContext(const Array<AudioSourceState>& states)
{ {
LOG(Info, "Rebuilding audio contexts"); LOG(Info, "Rebuilding audio contexts");
if (!isChangingDevice) ClearContext();
{
for (AudioSource* source : Audio::Sources)
source->Cleanup();
}
ClearContexts();
if (Device == nullptr) if (Device == nullptr)
return; return;
@@ -200,29 +140,45 @@ namespace ALC
attrList = attrsHrtf; attrList = attrsHrtf;
} }
#if ALC_MULTIPLE_LISTENERS Context = alcCreateContext(Device, attrList);
const int32 numListeners = Audio::Listeners.Count(); alcMakeContextCurrent(Context);
const int32 numContexts = numListeners > 1 ? numListeners : 1;
Contexts.Resize(numContexts);
ALC_FOR_EACH_CONTEXT()
ALCcontext* context = alcCreateContext(Device, attrList);
Contexts[i] = context;
}
#else
Contexts.Resize(1);
Contexts[0] = alcCreateContext(Device, attrList);
#endif
// If only one context is available keep it active as an optimization.
// Audio listeners and sources will avoid excessive context switching in such case.
alcMakeContextCurrent(Contexts[0]);
for (AudioListener* listener : Audio::Listeners) for (AudioListener* listener : Audio::Listeners)
Listener::Rebuild(listener); Listener::Rebuild(listener);
for (AudioSource* source : Audio::Sources) for (int32 i = 0; i < states.Count(); i++)
Source::Rebuild(source); {
AudioSource* source = Audio::Sources[i];
Source::Rebuild(source->SourceID, source->GetPosition(), source->GetOrientation(), source->GetVolume(), source->GetPitch(), source->GetPan(), source->GetIsLooping() && !source->UseStreaming(), source->Is3D(), source->GetAttenuation(), source->GetMinDistance(), source->GetDopplerFactor());
if (states.HasItems())
{
// Restore playback state
auto& state = states[i];
if (state.State != AudioSource::States::Stopped)
source->Play();
if (state.State == AudioSource::States::Paused)
source->Pause();
if (state.State != AudioSource::States::Stopped)
source->SetTime(state.Time);
}
}
}
void RebuildContext(bool isChangingDevice)
{
Array<AudioSourceState> states;
if (!isChangingDevice)
{
states.EnsureCapacity(Audio::Sources.Count());
for (AudioSource* source : Audio::Sources)
{
states.Add({ source->GetState(), source->GetTime() });
source->Stop();
}
}
RebuildContext(states);
} }
} }
@@ -313,308 +269,212 @@ const Char* GetOpenALErrorString(int error)
return TEXT("???"); return TEXT("???");
} }
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener) void AudioBackendOAL::Listener_Reset()
{ {
#if ALC_MULTIPLE_LISTENERS
ALC::RebuildContexts(false);
#else
AudioBackend::Listener::TransformChanged(listener);
alListenerf(AL_GAIN, Audio::GetVolume()); alListenerf(AL_GAIN, Audio::GetVolume());
#endif
} }
void AudioBackendOAL::Listener_OnRemove(AudioListener* listener) void AudioBackendOAL::Listener_VelocityChanged(const Vector3& velocity)
{ {
#if ALC_MULTIPLE_LISTENERS
ALC::RebuildContexts(false);
#endif
}
void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const Float3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity)); alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
} }
void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener) void AudioBackendOAL::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{ {
ALC_GET_LISTENER_CONTEXT(listener)
const Float3 position = listener->GetPosition();
const Quaternion orientation = listener->GetOrientation();
const Float3 flipX(-1, 1, 1); const Float3 flipX(-1, 1, 1);
const Float3 alOrientation[2] = const Float3 alOrientation[2] =
{ {
// Forward
orientation * Float3::Forward * flipX, orientation * Float3::Forward * flipX,
// Up
orientation * Float3::Up * flipX orientation * Float3::Up * flipX
}; };
alListenerfv(AL_ORIENTATION, (float*)alOrientation); alListenerfv(AL_ORIENTATION, (float*)alOrientation);
alListener3f(AL_POSITION, FLAX_POS_TO_OAL(position)); alListener3f(AL_POSITION, FLAX_POS_TO_OAL(position));
} }
void AudioBackendOAL::Listener_ReinitializeAll() void AudioBackendOAL::Listener_ReinitializeAll()
{ {
ALC::RebuildContexts(false); ALC::RebuildContext(false);
} }
void AudioBackendOAL::Source_OnAdd(AudioSource* source) uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{ {
ALC::Source::Rebuild(source); uint32 sourceID = 0;
ALC::Source::Rebuild(sourceID, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
// Cache audio data format assigned on source (used in Source_GetCurrentBufferTime)
ALC::Locker.Lock();
ALC::SourceIDtoFormat[sourceID] = format;
ALC::Locker.Unlock();
return sourceID;
} }
void AudioBackendOAL::Source_OnRemove(AudioSource* source) void AudioBackendOAL::Source_Remove(uint32 sourceID)
{ {
source->Cleanup(); alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
alDeleteSources(1, &sourceID);
ALC_CHECK_ERROR(alDeleteSources);
ALC::Locker.Lock();
ALC::SourceIDtoFormat.Remove(sourceID);
ALC::Locker.Unlock();
} }
void AudioBackendOAL::Source_VelocityChanged(AudioSource* source) void AudioBackendOAL::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{ {
if (!source->Is3D()) alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
} }
void AudioBackendOAL::Source_TransformChanged(AudioSource* source) void AudioBackendOAL::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{ {
if (!source->Is3D()) alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
}
} }
void AudioBackendOAL::Source_VolumeChanged(AudioSource* source) void AudioBackendOAL::Source_VolumeChanged(uint32 sourceID, float volume)
{ {
ALC_FOR_EACH_CONTEXT() alSourcef(sourceID, AL_GAIN, volume);
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_GAIN, source->GetVolume());
}
} }
void AudioBackendOAL::Source_PitchChanged(AudioSource* source) void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch)
{ {
ALC_FOR_EACH_CONTEXT() alSourcef(sourceID, AL_PITCH, pitch);
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_PITCH, source->GetPitch());
}
} }
void AudioBackendOAL::Source_PanChanged(AudioSource* source) void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan)
{ {
#ifdef AL_EXT_STEREO_ANGLES #ifdef AL_EXT_STEREO_ANGLES
const float panAngle = source->GetPan() * PI_HALF; const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
ALC_FOR_EACH_CONTEXT() alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
const uint32 sourceID = source->SourceIDs[i];
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
}
#endif #endif
} }
void AudioBackendOAL::Source_IsLoopingChanged(AudioSource* source) void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{ {
const bool loop = source->GetIsLooping() && !source->UseStreaming(); alSourcei(sourceID, AL_LOOPING, loop);
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_LOOPING, loop);
}
} }
void AudioBackendOAL::Source_SpatialSetupChanged(AudioSource* source) void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
const bool is3D = source->Is3D(); alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial);
ALC_FOR_EACH_CONTEXT() if (spatial)
const uint32 sourceID = source->SourceIDs[i]; {
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
if (is3D)
{
#ifdef AL_SOFT_source_spatialize #ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif #endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation()); alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, source->GetDopplerFactor()); alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance())); alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
} }
else else
{ {
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f); alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f); alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f); alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
} }
} }
void AudioBackendOAL::Source_ClipLoaded(AudioSource* source) void AudioBackendOAL::Source_Play(uint32 sourceID)
{ {
if (source->SourceIDs.Count() < ALC::Contexts.Count()) alSourcePlay(sourceID);
return; ALC_CHECK_ERROR(alSourcePlay);
const auto clip = source->Clip.Get();
const bool is3D = source->Is3D();
const bool loop = source->GetIsLooping() && !clip->IsStreamable();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_LOOPING, loop);
}
} }
void AudioBackendOAL::Source_Cleanup(AudioSource* source) void AudioBackendOAL::Source_Pause(uint32 sourceID)
{ {
ALC_FOR_EACH_CONTEXT() alSourcePause(sourceID);
const uint32 sourceID = source->SourceIDs[i]; ALC_CHECK_ERROR(alSourcePause);
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
alDeleteSources(1, &sourceID);
ALC_CHECK_ERROR(alDeleteSources);
}
} }
void AudioBackendOAL::Source_Play(AudioSource* source) void AudioBackendOAL::Source_Stop(uint32 sourceID)
{ {
ALC_FOR_EACH_CONTEXT() // Stop and rewind
const uint32 sourceID = source->SourceIDs[i]; alSourceRewind(sourceID);
ALC_CHECK_ERROR(alSourceRewind);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
// Play // Unset streaming buffers
alSourcePlay(sourceID); alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcePlay); ALC_CHECK_ERROR(alSourcei);
}
} }
void AudioBackendOAL::Source_Pause(AudioSource* source) void AudioBackendOAL::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{ {
ALC_FOR_EACH_CONTEXT() alSourcef(sourceID, AL_SEC_OFFSET, value);
const uint32 sourceID = source->SourceIDs[i];
// Pause
alSourcePause(sourceID);
ALC_CHECK_ERROR(alSourcePause);
}
} }
void AudioBackendOAL::Source_Stop(AudioSource* source) float AudioBackendOAL::Source_GetCurrentBufferTime(uint32 sourceID)
{ {
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
// Stop and rewind
alSourceRewind(sourceID);
ALC_CHECK_ERROR(alSourceRewind);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
// Unset streaming buffers
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
}
}
void AudioBackendOAL::Source_SetCurrentBufferTime(AudioSource* source, float value)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_SEC_OFFSET, value);
}
}
float AudioBackendOAL::Source_GetCurrentBufferTime(const AudioSource* source)
{
ALC_GET_DEFAULT_CONTEXT()
#if 0 #if 0
float time; float time;
alGetSourcef(source->SourceIDs[0], AL_SEC_OFFSET, &time); alGetSourcef(sourceID, AL_SEC_OFFSET, &time);
#else #else
ASSERT(source->Clip && source->Clip->IsLoaded()); ALC::Locker.Lock();
const AudioDataInfo& clipInfo = source->Clip->AudioHeader.Info; AudioDataInfo clipInfo = ALC::SourceIDtoFormat[sourceID];
ALC::Locker.Unlock();
ALint samplesPlayed; ALint samplesPlayed;
alGetSourcei(source->SourceIDs[0], AL_SAMPLE_OFFSET, &samplesPlayed); alGetSourcei(sourceID, AL_SAMPLE_OFFSET, &samplesPlayed);
const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels; const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
const float time = (samplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, clipInfo.SampleRate)); if (totalSamples > 0)
samplesPlayed %= totalSamples;
const float time = samplesPlayed / static_cast<float>(Math::Max(1U, clipInfo.SampleRate));
#endif #endif
return time; return time;
} }
void AudioBackendOAL::Source_SetNonStreamingBuffer(AudioSource* source) void AudioBackendOAL::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{ {
const uint32 bufferId = source->Clip->Buffers[0]; alSourcei(sourceID, AL_BUFFER, bufferID);
ALC_FOR_EACH_CONTEXT() ALC_CHECK_ERROR(alSourcei);
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_BUFFER, bufferId);
ALC_CHECK_ERROR(alSourcei);
}
} }
void AudioBackendOAL::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) void AudioBackendOAL::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{ {
ALC_GET_DEFAULT_CONTEXT()
// Check the first context only // Check the first context only
const uint32 sourceID = source->SourceIDs[0];
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &processedBuffersCount); alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &processedBuffersCount);
ALC_CHECK_ERROR(alGetSourcei); ALC_CHECK_ERROR(alGetSourcei);
} }
void AudioBackendOAL::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) void AudioBackendOAL::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{ {
ALC_GET_DEFAULT_CONTEXT()
// Check the first context only // Check the first context only
const uint32 sourceID = source->SourceIDs[0];
alGetSourcei(sourceID, AL_BUFFERS_QUEUED, &queuedBuffersCount); alGetSourcei(sourceID, AL_BUFFERS_QUEUED, &queuedBuffersCount);
ALC_CHECK_ERROR(alGetSourcei); ALC_CHECK_ERROR(alGetSourcei);
} }
void AudioBackendOAL::Source_QueueBuffer(AudioSource* source, uint32 bufferId) void AudioBackendOAL::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{ {
ALC_FOR_EACH_CONTEXT() // Queue new buffer
const uint32 sourceID = source->SourceIDs[i]; alSourceQueueBuffers(sourceID, 1, &bufferID);
ALC_CHECK_ERROR(alSourceQueueBuffers);
// Queue new buffer
alSourceQueueBuffers(sourceID, 1, &bufferId);
ALC_CHECK_ERROR(alSourceQueueBuffers);
}
} }
void AudioBackendOAL::Source_DequeueProcessedBuffers(AudioSource* source) void AudioBackendOAL::Source_DequeueProcessedBuffers(uint32 sourceID)
{ {
ALuint buffers[AUDIO_MAX_SOURCE_BUFFERS]; int32 numProcessedBuffers;
ALC_FOR_EACH_CONTEXT() alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
const uint32 sourceID = source->SourceIDs[i]; Array<ALuint, InlinedAllocation<AUDIO_MAX_SOURCE_BUFFERS>> buffers;
buffers.Resize(numProcessedBuffers);
int32 numProcessedBuffers; alSourceUnqueueBuffers(sourceID, numProcessedBuffers, buffers.Get());
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &numProcessedBuffers); ALC_CHECK_ERROR(alSourceUnqueueBuffers);
alSourceUnqueueBuffers(sourceID, numProcessedBuffers, buffers);
ALC_CHECK_ERROR(alSourceUnqueueBuffers);
}
} }
uint32 AudioBackendOAL::Buffer_Create() uint32 AudioBackendOAL::Buffer_Create()
{ {
uint32 bufferId; uint32 bufferID;
alGenBuffers(1, &bufferId); alGenBuffers(1, &bufferID);
ALC_CHECK_ERROR(alGenBuffers); ALC_CHECK_ERROR(alGenBuffers);
return bufferId; return bufferID;
} }
void AudioBackendOAL::Buffer_Delete(uint32 bufferId) void AudioBackendOAL::Buffer_Delete(uint32 bufferID)
{ {
alDeleteBuffers(1, &bufferId); alDeleteBuffers(1, &bufferID);
ALC_CHECK_ERROR(alDeleteBuffers); ALC_CHECK_ERROR(alDeleteBuffers);
} }
void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{ {
PROFILE_CPU(); PROFILE_CPU();
@@ -633,19 +493,19 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples); AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 32); format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate); alBufferData(bufferID, format, sampleBufferFloat, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBufferFloat); Allocator::Free(sampleBufferFloat);
} }
else else
{ {
LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated."); LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Audio data will be truncated.");
const uint32 bufferSize = info.NumSamples * 2; const uint32 bufferSize = info.NumSamples * 2;
byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize); byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize);
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples); AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 16); format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate); alBufferData(bufferID, format, sampleBuffer16, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer16); Allocator::Free(sampleBuffer16);
} }
@@ -658,13 +518,13 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
for (uint32 i = 0; i < info.NumSamples; i++) for (uint32 i = 0; i < info.NumSamples; i++)
sampleBuffer[i] = ((int8*)samples)[i] + 128; sampleBuffer[i] = ((int8*)samples)[i] + 128;
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); alBufferData(bufferID, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer); Allocator::Free(sampleBuffer);
} }
else if (format) else if (format)
{ {
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); alBufferData(bufferID, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
} }
} }
@@ -681,7 +541,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples); AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 32); format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate); alBufferData(bufferID, format, sampleBuffer32, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer32); Allocator::Free(sampleBuffer32);
@@ -696,14 +556,14 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
sampleBuffer[i] = ((int8*)samples)[i] + 128; sampleBuffer[i] = ((int8*)samples)[i] + 128;
format = GetOpenALBufferFormat(info.NumChannels, 16); format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); alBufferData(bufferID, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer); Allocator::Free(sampleBuffer);
} }
else if (format) else if (format)
{ {
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); alBufferData(bufferID, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
} }
} }
@@ -727,9 +587,19 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged() void AudioBackendOAL::Base_OnActiveDeviceChanged()
{ {
// Cleanup // Cleanup
Array<ALC::AudioSourceState> states;
states.EnsureCapacity(Audio::Sources.Count());
for (AudioSource* source : Audio::Sources) for (AudioSource* source : Audio::Sources)
source->Cleanup(); {
ALC::ClearContexts(); states.Add({ source->GetState(), source->GetTime() });
source->Stop();
if (source->SourceID)
{
Source_Remove(source->SourceID);
source->SourceID = 0;
}
}
ALC::ClearContext();
if (ALC::Device != nullptr) if (ALC::Device != nullptr)
{ {
alcCloseDevice(ALC::Device); alcCloseDevice(ALC::Device);
@@ -746,7 +616,7 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
} }
// Setup // Setup
ALC::RebuildContexts(true); ALC::RebuildContext(states);
} }
void AudioBackendOAL::Base_SetDopplerFactor(float value) void AudioBackendOAL::Base_SetDopplerFactor(float value)
@@ -756,9 +626,7 @@ void AudioBackendOAL::Base_SetDopplerFactor(float value)
void AudioBackendOAL::Base_SetVolume(float value) void AudioBackendOAL::Base_SetVolume(float value)
{ {
ALC_FOR_EACH_CONTEXT() alListenerf(AL_GAIN, value);
alListenerf(AL_GAIN, value);
}
} }
bool AudioBackendOAL::Base_Init() bool AudioBackendOAL::Base_Init()
@@ -862,7 +730,7 @@ bool AudioBackendOAL::Base_Init()
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1); int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
if (clampedIndex == Audio::GetActiveDeviceIndex()) if (clampedIndex == Audio::GetActiveDeviceIndex())
{ {
ALC::RebuildContexts(true); ALC::RebuildContext(true);
} }
Audio::SetActiveDeviceIndex(activeDeviceIndex); Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize #ifdef AL_SOFT_source_spatialize

View File

@@ -12,37 +12,33 @@
class AudioBackendOAL : public AudioBackend class AudioBackendOAL : public AudioBackend
{ {
public: public:
// [AudioBackend] // [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override; void Listener_Reset() override;
void Listener_OnRemove(AudioListener* listener) override; void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_VelocityChanged(AudioListener* listener) override; void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override; void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override; uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_OnRemove(AudioSource* source) override; void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(AudioSource* source) override; void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(AudioSource* source) override; void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(AudioSource* source) override; void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(AudioSource* source) override; void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(AudioSource* source) override; void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(AudioSource* source) override; void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(AudioSource* source) override; void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_ClipLoaded(AudioSource* source) override; void Source_Play(uint32 sourceID) override;
void Source_Cleanup(AudioSource* source) override; void Source_Pause(uint32 sourceID) override;
void Source_Play(AudioSource* source) override; void Source_Stop(uint32 sourceID) override;
void Source_Pause(AudioSource* source) override; void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
void Source_Stop(AudioSource* source) override; float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override; void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override; void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override; void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override; void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override; void Source_DequeueProcessedBuffers(uint32 sourceID) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Buffer_Create() override; uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override; void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override; void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override; const Char* Base_Name() override;
FeatureFlags Base_Features() override; FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override; void Base_OnActiveDeviceChanged() override;

View File

@@ -8,8 +8,6 @@
#include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Collections/ChunkedArray.h"
#include "Engine/Core/Log.h" #include "Engine/Core/Log.h"
#include "Engine/Audio/Audio.h" #include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Audio/AudioListener.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
@@ -21,11 +19,9 @@
// Include XAudio library // Include XAudio library
// Documentation: https://docs.microsoft.com/en-us/windows/desktop/xaudio2/xaudio2-apis-portal // Documentation: https://docs.microsoft.com/en-us/windows/desktop/xaudio2/xaudio2-apis-portal
#include <xaudio2.h> #include <xaudio2.h>
//#include <xaudio2fx.h>
//#include <x3daudio.h>
// TODO: implement multi-channel support (eg. 5.1, 7.1) // TODO: implement multi-channel support (eg. 5.1, 7.1)
#define MAX_INPUT_CHANNELS 2 #define MAX_INPUT_CHANNELS 6
#define MAX_OUTPUT_CHANNELS 2 #define MAX_OUTPUT_CHANNELS 2
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS) #define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
#if ENABLE_ASSERTION #if ENABLE_ASSERTION
@@ -42,33 +38,6 @@ namespace XAudio2
{ {
struct Listener : AudioBackendTools::Listener struct Listener : AudioBackendTools::Listener
{ {
AudioListener* AudioListener;
Listener()
{
Init();
}
void Init()
{
AudioListener = nullptr;
}
bool IsFree() const
{
return AudioListener == nullptr;
}
void UpdateTransform()
{
Position = AudioListener->GetPosition();
Orientation = AudioListener->GetOrientation();
}
void UpdateVelocity()
{
Velocity = AudioListener->GetVelocity();
}
}; };
class VoiceCallback : public IXAudio2VoiceCallback class VoiceCallback : public IXAudio2VoiceCallback
@@ -104,7 +73,7 @@ namespace XAudio2
} }
public: public:
AudioSource* Source; uint32 SourceID;
void PeekSamples(); void PeekSamples();
}; };
@@ -113,6 +82,7 @@ namespace XAudio2
{ {
IXAudio2SourceVoice* Voice; IXAudio2SourceVoice* Voice;
WAVEFORMATEX Format; WAVEFORMATEX Format;
AudioDataInfo Info;
XAUDIO2_SEND_DESCRIPTOR Destination; XAUDIO2_SEND_DESCRIPTOR Destination;
float StartTimeForQueueBuffer; float StartTimeForQueueBuffer;
float LastBufferStartTime; float LastBufferStartTime;
@@ -121,6 +91,8 @@ namespace XAudio2
int32 Channels; int32 Channels;
bool IsDirty; bool IsDirty;
bool IsPlaying; bool IsPlaying;
bool IsLoop;
uint32 LastBufferID;
VoiceCallback Callback; VoiceCallback Callback;
Source() Source()
@@ -140,6 +112,8 @@ namespace XAudio2
IsDirty = false; IsDirty = false;
Is3D = false; Is3D = false;
IsPlaying = false; IsPlaying = false;
IsLoop = false;
LastBufferID = 0;
LastBufferStartSamplesPlayed = 0; LastBufferStartSamplesPlayed = 0;
BuffersProcessed = 0; BuffersProcessed = 0;
} }
@@ -148,17 +122,6 @@ namespace XAudio2
{ {
return Voice == nullptr; return Voice == nullptr;
} }
void UpdateTransform(const AudioSource* source)
{
Position = source->GetPosition();
Orientation = source->GetOrientation();
}
void UpdateVelocity(const AudioSource* source)
{
Velocity = source->GetVelocity();
}
}; };
struct Buffer struct Buffer
@@ -186,43 +149,20 @@ namespace XAudio2
IXAudio2* Instance = nullptr; IXAudio2* Instance = nullptr;
IXAudio2MasteringVoice* MasteringVoice = nullptr; IXAudio2MasteringVoice* MasteringVoice = nullptr;
int32 Channels; int32 Channels;
DWORD ChannelMask;
bool ForceDirty = true; bool ForceDirty = true;
AudioBackendTools::Settings Settings; AudioBackendTools::Settings Settings;
Listener Listeners[AUDIO_MAX_LISTENERS]; Listener Listener;
CriticalSection Locker; CriticalSection Locker;
ChunkedArray<Source, 32> Sources; ChunkedArray<Source, 32> Sources;
ChunkedArray<Buffer*, 64> Buffers; // TODO: use ChunkedArray for better performance or use buffers pool? ChunkedArray<Buffer*, 64> Buffers; // TODO: use ChunkedArray for better performance or use buffers pool?
EngineCallback Callback; EngineCallback Callback;
Listener* GetListener() Source* GetSource(uint32 sourceID)
{ {
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++) if (sourceID == 0)
{
if (Listeners[i].AudioListener)
return &Listeners[i];
}
return nullptr;
}
Listener* GetListener(const AudioListener* listener)
{
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++)
{
if (Listeners[i].AudioListener == listener)
return &Listeners[i];
}
return nullptr;
}
Source* GetSource(const AudioSource* source)
{
if (source->SourceIDs.Count() == 0)
return nullptr; return nullptr;
const AUDIO_SOURCE_ID_TYPE sourceId = source->SourceIDs[0]; return &Sources[sourceID - 1]; // 0 is invalid ID so shift them
// 0 is invalid ID so shift them
return &Sources[sourceId - 1];
} }
void MarkAllDirty() void MarkAllDirty()
@@ -230,9 +170,9 @@ namespace XAudio2
ForceDirty = true; ForceDirty = true;
} }
void QueueBuffer(Source* aSource, const AudioSource* source, const int32 bufferId, XAUDIO2_BUFFER& buffer) void QueueBuffer(Source* aSource, const int32 bufferID, XAUDIO2_BUFFER& buffer)
{ {
Buffer* aBuffer = Buffers[bufferId - 1]; Buffer* aBuffer = Buffers[bufferID - 1];
buffer.pAudioData = aBuffer->Data.Get(); buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count(); buffer.AudioBytes = aBuffer->Data.Count();
@@ -252,70 +192,37 @@ namespace XAudio2
void VoiceCallback::OnBufferEnd(void* pBufferContext) void VoiceCallback::OnBufferEnd(void* pBufferContext)
{ {
auto aSource = GetSource(Source); auto aSource = GetSource(SourceID);
if (aSource->IsPlaying) if (aSource->IsPlaying)
aSource->BuffersProcessed++; aSource->BuffersProcessed++;
} }
void VoiceCallback::PeekSamples() void VoiceCallback::PeekSamples()
{ {
auto aSource = GetSource(Source); auto aSource = GetSource(SourceID);
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
aSource->Voice->GetState(&state); aSource->Voice->GetState(&state);
aSource->LastBufferStartSamplesPlayed = state.SamplesPlayed; aSource->LastBufferStartSamplesPlayed = state.SamplesPlayed;
} }
} }
void AudioBackendXAudio2::Listener_OnAdd(AudioListener* listener) void AudioBackendXAudio2::Listener_Reset()
{ {
// Get first free listener XAudio2::Listener.Reset();
XAudio2::Listener* aListener = nullptr;
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++)
{
if (XAudio2::Listeners[i].IsFree())
{
aListener = &XAudio2::Listeners[i];
break;
}
}
ASSERT(aListener);
// Setup
aListener->AudioListener = listener;
aListener->UpdateTransform();
aListener->UpdateVelocity();
XAudio2::MarkAllDirty(); XAudio2::MarkAllDirty();
} }
void AudioBackendXAudio2::Listener_OnRemove(AudioListener* listener) void AudioBackendXAudio2::Listener_VelocityChanged(const Vector3& velocity)
{ {
XAudio2::Listener* aListener = XAudio2::GetListener(listener); XAudio2::Listener.Velocity = velocity;
if (aListener) XAudio2::MarkAllDirty();
{
aListener->Init();
XAudio2::MarkAllDirty();
}
} }
void AudioBackendXAudio2::Listener_VelocityChanged(AudioListener* listener) void AudioBackendXAudio2::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{ {
XAudio2::Listener* aListener = XAudio2::GetListener(listener); XAudio2::Listener.Position = position;
if (aListener) XAudio2::Listener.Orientation = orientation;
{ XAudio2::MarkAllDirty();
aListener->UpdateVelocity();
XAudio2::MarkAllDirty();
}
}
void AudioBackendXAudio2::Listener_TransformChanged(AudioListener* listener)
{
XAudio2::Listener* aListener = XAudio2::GetListener(listener);
if (aListener)
{
aListener->UpdateTransform();
XAudio2::MarkAllDirty();
}
} }
void AudioBackendXAudio2::Listener_ReinitializeAll() void AudioBackendXAudio2::Listener_ReinitializeAll()
@@ -323,17 +230,13 @@ void AudioBackendXAudio2::Listener_ReinitializeAll()
// TODO: Implement XAudio2 reinitialization; read HRTF audio value from Audio class // TODO: Implement XAudio2 reinitialization; read HRTF audio value from Audio class
} }
void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) uint32 AudioBackendXAudio2::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{ {
// Skip if has no clip (needs audio data to create a source - needs data format information)
if (source->Clip == nullptr || !source->Clip->IsLoaded())
return;
auto clip = source->Clip.Get();
ScopeLock lock(XAudio2::Locker); ScopeLock lock(XAudio2::Locker);
// Get first free source // Get first free source
XAudio2::Source* aSource = nullptr; XAudio2::Source* aSource = nullptr;
AUDIO_SOURCE_ID_TYPE sourceID; uint32 sourceID = 0;
for (int32 i = 0; i < XAudio2::Sources.Count(); i++) for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
{ {
if (XAudio2::Sources[i].IsFree()) if (XAudio2::Sources[i].IsFree())
@@ -351,116 +254,124 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
XAudio2::Sources.Add(src); XAudio2::Sources.Add(src);
aSource = &XAudio2::Sources[sourceID]; aSource = &XAudio2::Sources[sourceID];
} }
sourceID++; // 0 is invalid ID so shift them
// Initialize audio data format information (from clip) // Initialize audio data format information (from clip)
const auto& header = clip->AudioHeader; aSource->Info = format;
auto& format = aSource->Format; auto& aFormat = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM; aFormat.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset) aFormat.nChannels = spatial ? 1 : format.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset)
format.nSamplesPerSec = header.Info.SampleRate; aFormat.nSamplesPerSec = format.SampleRate;
format.wBitsPerSample = header.Info.BitDepth; aFormat.wBitsPerSample = format.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8)); aFormat.nBlockAlign = (WORD)(aFormat.nChannels * (aFormat.wBitsPerSample / 8));
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; aFormat.nAvgBytesPerSec = aFormat.nSamplesPerSec * aFormat.nBlockAlign;
format.cbSize = 0; aFormat.cbSize = 0;
// Setup dry effect // Setup dry effect
aSource->Destination.pOutputVoice = XAudio2::MasteringVoice; aSource->Destination.pOutputVoice = XAudio2::MasteringVoice;
// Create voice // Create voice
const XAUDIO2_VOICE_SENDS sendList = const XAUDIO2_VOICE_SENDS sendList = { 1, &aSource->Destination };
{
1,
&aSource->Destination
};
HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList); HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
XAUDIO2_CHECK_ERROR(CreateSourceVoice); XAUDIO2_CHECK_ERROR(CreateSourceVoice);
if (FAILED(hr)) if (FAILED(hr))
return; return 0;
sourceID++; // 0 is invalid ID so shift them
source->SourceIDs.Add(sourceID);
// Prepare source state // Prepare source state
aSource->Callback.Source = source; aSource->Callback.SourceID = sourceID;
aSource->IsDirty = true; aSource->IsDirty = true;
aSource->Is3D = source->Is3D(); aSource->IsLoop = loop;
aSource->Pitch = source->GetPitch(); aSource->Is3D = spatial;
aSource->Pan = source->GetPan(); aSource->Pitch = pitch;
aSource->DopplerFactor = source->GetDopplerFactor(); aSource->Pan = pan;
aSource->Volume = source->GetVolume(); aSource->DopplerFactor = doppler;
aSource->MinDistance = source->GetMinDistance(); aSource->Volume = volume;
aSource->Attenuation = source->GetAttenuation(); aSource->MinDistance = minDistance;
aSource->Channels = format.nChannels; aSource->Attenuation = attenuation;
aSource->UpdateTransform(source); aSource->Channels = aFormat.nChannels;
aSource->UpdateVelocity(source); aSource->Position = position;
hr = aSource->Voice->SetVolume(source->GetVolume()); aSource->Orientation = orientation;
aSource->Velocity = Vector3::Zero;
hr = aSource->Voice->SetVolume(volume);
XAUDIO2_CHECK_ERROR(SetVolume); XAUDIO2_CHECK_ERROR(SetVolume);
source->Restore(); return sourceID;
} }
void AudioBackendXAudio2::Source_OnRemove(AudioSource* source) void AudioBackendXAudio2::Source_Remove(uint32 sourceID)
{ {
ScopeLock lock(XAudio2::Locker); ScopeLock lock(XAudio2::Locker);
source->Cleanup(); auto aSource = XAudio2::GetSource(sourceID);
if (!aSource)
return;
// Free source
if (aSource->Voice)
{
aSource->Voice->DestroyVoice();
}
aSource->Init();
} }
void AudioBackendXAudio2::Source_VelocityChanged(AudioSource* source) void AudioBackendXAudio2::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
aSource->UpdateVelocity(source); aSource->Velocity = velocity;
aSource->IsDirty = true; aSource->IsDirty = true;
} }
} }
void AudioBackendXAudio2::Source_TransformChanged(AudioSource* source) void AudioBackendXAudio2::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
aSource->UpdateTransform(source); aSource->Position = position;
aSource->Orientation = orientation;
aSource->IsDirty = true; aSource->IsDirty = true;
} }
} }
void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source) void AudioBackendXAudio2::Source_VolumeChanged(uint32 sourceID, float volume)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
aSource->Volume = source->GetVolume(); aSource->Volume = volume;
const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume()); const HRESULT hr = aSource->Voice->SetVolume(volume);
XAUDIO2_CHECK_ERROR(SetVolume); XAUDIO2_CHECK_ERROR(SetVolume);
} }
} }
void AudioBackendXAudio2::Source_PitchChanged(AudioSource* source) void AudioBackendXAudio2::Source_PitchChanged(uint32 sourceID, float pitch)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
aSource->Pitch = source->GetPitch(); aSource->Pitch = pitch;
aSource->IsDirty = true; aSource->IsDirty = true;
} }
} }
void AudioBackendXAudio2::Source_PanChanged(AudioSource* source) void AudioBackendXAudio2::Source_PanChanged(uint32 sourceID, float pan)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
aSource->Pan = source->GetPan(); aSource->Pan = pan;
aSource->IsDirty = true; aSource->IsDirty = true;
} }
} }
void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source) void AudioBackendXAudio2::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{ {
auto aSource = XAudio2::GetSource(source); ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(sourceID);
if (!aSource || !aSource->Voice) if (!aSource || !aSource->Voice)
return; return;
aSource->IsLoop = loop;
// Skip if has no buffers (waiting for data or sth) // Skip if has no buffers (waiting for data or sth)
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
@@ -468,15 +379,12 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
if (state.BuffersQueued == 0) if (state.BuffersQueued == 0)
return; return;
// Looping is defined during buffer submission so reset source buffer (this is called only for non-streamable sources that ue single buffer) // Looping is defined during buffer submission so reset source buffer (this is called only for non-streamable sources that use a single buffer)
const uint32 bufferID = aSource->LastBufferID;
XAudio2::Locker.Lock(); XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
const uint32 bufferId = source->Clip->Buffers[0];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
HRESULT hr; HRESULT hr;
const bool isPlaying = source->IsActuallyPlayingSth(); const bool isPlaying = aSource->IsPlaying;
if (isPlaying) if (isPlaying)
{ {
hr = aSource->Voice->Stop(); hr = aSource->Voice->Stop();
@@ -492,7 +400,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
XAUDIO2_BUFFER buffer = { 0 }; XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer; buffer.pContext = aBuffer;
buffer.Flags = XAUDIO2_END_OF_STREAM; buffer.Flags = XAUDIO2_END_OF_STREAM;
if (source->GetIsLooping()) if (loop)
buffer.LoopCount = XAUDIO2_LOOP_INFINITE; buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
// Restore play position // Restore play position
@@ -501,7 +409,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
buffer.PlayLength = totalSamples - buffer.PlayBegin; buffer.PlayLength = totalSamples - buffer.PlayBegin;
aSource->StartTimeForQueueBuffer = 0; aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer); XAudio2::QueueBuffer(aSource, bufferID, buffer);
if (isPlaying) if (isPlaying)
{ {
@@ -510,48 +418,22 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
} }
} }
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source) void AudioBackendXAudio2::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
aSource->Is3D = source->Is3D(); aSource->Is3D = spatial;
aSource->MinDistance = source->GetMinDistance(); aSource->MinDistance = minDistance;
aSource->Attenuation = source->GetAttenuation(); aSource->Attenuation = attenuation;
aSource->DopplerFactor = source->GetDopplerFactor(); aSource->DopplerFactor = doppler;
aSource->IsDirty = true; aSource->IsDirty = true;
} }
} }
void AudioBackendXAudio2::Source_ClipLoaded(AudioSource* source) void AudioBackendXAudio2::Source_Play(uint32 sourceID)
{ {
ScopeLock lock(XAudio2::Locker); auto aSource = XAudio2::GetSource(sourceID);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
{
// Register source if clip was missing
Source_OnAdd(source);
}
}
void AudioBackendXAudio2::Source_Cleanup(AudioSource* source)
{
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
return;
// Free source
if (aSource->Voice)
{
aSource->Voice->DestroyVoice();
}
aSource->Init();
}
void AudioBackendXAudio2::Source_Play(AudioSource* source)
{
auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice && !aSource->IsPlaying) if (aSource && aSource->Voice && !aSource->IsPlaying)
{ {
// Play // Play
@@ -561,9 +443,9 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source)
} }
} }
void AudioBackendXAudio2::Source_Pause(AudioSource* source) void AudioBackendXAudio2::Source_Pause(uint32 sourceID)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice && aSource->IsPlaying) if (aSource && aSource->Voice && aSource->IsPlaying)
{ {
// Pause // Pause
@@ -573,9 +455,9 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source)
} }
} }
void AudioBackendXAudio2::Source_Stop(AudioSource* source) void AudioBackendXAudio2::Source_Stop(uint32 sourceID)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
aSource->StartTimeForQueueBuffer = 0.0f; aSource->StartTimeForQueueBuffer = 0.0f;
@@ -595,9 +477,9 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
} }
} }
void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float value) void AudioBackendXAudio2::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{ {
const auto aSource = XAudio2::GetSource(source); const auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
// Store start time so next buffer submitted will start from here (assumes audio is stopped) // Store start time so next buffer submitted will start from here (assumes audio is stopped)
@@ -605,60 +487,63 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
} }
} }
float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source) float AudioBackendXAudio2::Source_GetCurrentBufferTime(uint32 sourceID)
{ {
float time = 0; float time = 0;
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource) if (aSource)
{ {
ASSERT(source->Clip && source->Clip->IsLoaded()); const auto& clipInfo = aSource->Info;
const auto& clipInfo = source->Clip->AudioHeader.Info;
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
aSource->Voice->GetState(&state); aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels; const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
const uint32 sampleRate = clipInfo.SampleRate; // / clipInfo.NumChannels; const uint32 sampleRate = clipInfo.SampleRate; // / clipInfo.NumChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin uint64 lastBufferStartSamplesPlayed = aSource->LastBufferStartSamplesPlayed;
time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, sampleRate)); if (totalSamples > 0)
lastBufferStartSamplesPlayed %= totalSamples;
state.SamplesPlayed -= lastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
if (totalSamples > 0)
state.SamplesPlayed %= totalSamples;
time = aSource->LastBufferStartTime + state.SamplesPlayed / static_cast<float>(Math::Max(1U, sampleRate));
} }
return time; return time;
} }
void AudioBackendXAudio2::Source_SetNonStreamingBuffer(AudioSource* source) void AudioBackendXAudio2::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (!aSource) if (!aSource)
return; return;
aSource->LastBufferID = bufferID; // Use for looping change
XAudio2::Locker.Lock(); XAudio2::Locker.Lock();
const uint32 bufferId = source->Clip->Buffers[0]; XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock(); XAudio2::Locker.Unlock();
XAUDIO2_BUFFER buffer = { 0 }; XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer; buffer.pContext = aBuffer;
buffer.Flags = XAUDIO2_END_OF_STREAM; buffer.Flags = XAUDIO2_END_OF_STREAM;
if (source->GetIsLooping()) if (aSource->IsLoop)
buffer.LoopCount = XAUDIO2_LOOP_INFINITE; buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
// Queue single buffer // Queue single buffer
XAudio2::QueueBuffer(aSource, source, bufferId, buffer); XAudio2::QueueBuffer(aSource, bufferID, buffer);
} }
void AudioBackendXAudio2::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) void AudioBackendXAudio2::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{ {
processedBuffersCount = 0; processedBuffersCount = 0;
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
processedBuffersCount = aSource->BuffersProcessed; processedBuffersCount = aSource->BuffersProcessed;
} }
} }
void AudioBackendXAudio2::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) void AudioBackendXAudio2::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{ {
queuedBuffersCount = 0; queuedBuffersCount = 0;
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_STATE state;
@@ -667,23 +552,24 @@ void AudioBackendXAudio2::Source_GetQueuedBuffersCount(AudioSource* source, int3
} }
} }
void AudioBackendXAudio2::Source_QueueBuffer(AudioSource* source, uint32 bufferId) void AudioBackendXAudio2::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (!aSource) if (!aSource)
return; return;
aSource->LastBufferID = bufferID; // Use for looping change
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAUDIO2_BUFFER buffer = { 0 }; XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer; buffer.pContext = aBuffer;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer); XAudio2::QueueBuffer(aSource, bufferID, buffer);
} }
void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source) void AudioBackendXAudio2::Source_DequeueProcessedBuffers(uint32 sourceID)
{ {
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
const HRESULT hr = aSource->Voice->FlushSourceBuffers(); const HRESULT hr = aSource->Voice->FlushSourceBuffers();
@@ -694,7 +580,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
uint32 AudioBackendXAudio2::Buffer_Create() uint32 AudioBackendXAudio2::Buffer_Create()
{ {
uint32 bufferId; uint32 bufferID;
ScopeLock lock(XAudio2::Locker); ScopeLock lock(XAudio2::Locker);
// Get first free buffer slot // Get first free buffer slot
@@ -705,7 +591,7 @@ uint32 AudioBackendXAudio2::Buffer_Create()
{ {
aBuffer = New<XAudio2::Buffer>(); aBuffer = New<XAudio2::Buffer>();
XAudio2::Buffers[i] = aBuffer; XAudio2::Buffers[i] = aBuffer;
bufferId = i + 1; bufferID = i + 1;
break; break;
} }
} }
@@ -714,28 +600,28 @@ uint32 AudioBackendXAudio2::Buffer_Create()
// Add new slot // Add new slot
aBuffer = New<XAudio2::Buffer>(); aBuffer = New<XAudio2::Buffer>();
XAudio2::Buffers.Add(aBuffer); XAudio2::Buffers.Add(aBuffer);
bufferId = XAudio2::Buffers.Count(); bufferID = XAudio2::Buffers.Count();
} }
aBuffer->Data.Resize(0); aBuffer->Data.Resize(0);
return bufferId; return bufferID;
} }
void AudioBackendXAudio2::Buffer_Delete(uint32 bufferId) void AudioBackendXAudio2::Buffer_Delete(uint32 bufferID)
{ {
ScopeLock lock(XAudio2::Locker); ScopeLock lock(XAudio2::Locker);
XAudio2::Buffer*& aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Buffer*& aBuffer = XAudio2::Buffers[bufferID - 1];
aBuffer->Data.Resize(0); aBuffer->Data.Resize(0);
Delete(aBuffer); Delete(aBuffer);
aBuffer = nullptr; aBuffer = nullptr;
} }
void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) void AudioBackendXAudio2::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{ {
CHECK(info.NumChannels <= MAX_INPUT_CHANNELS); CHECK(info.NumChannels <= MAX_INPUT_CHANNELS);
XAudio2::Locker.Lock(); XAudio2::Locker.Lock();
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAudio2::Locker.Unlock(); XAudio2::Locker.Unlock();
const uint32 samplesLength = info.NumSamples * info.BitDepth / 8; const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
@@ -796,7 +682,7 @@ bool AudioBackendXAudio2::Base_Init()
} }
XAUDIO2_VOICE_DETAILS details; XAUDIO2_VOICE_DETAILS details;
XAudio2::MasteringVoice->GetVoiceDetails(&details); XAudio2::MasteringVoice->GetVoiceDetails(&details);
#if 0 #if MAX_OUTPUT_CHANNELS > 2
// TODO: implement multi-channel support (eg. 5.1, 7.1) // TODO: implement multi-channel support (eg. 5.1, 7.1)
XAudio2::Channels = details.InputChannels; XAudio2::Channels = details.InputChannels;
hr = XAudio2::MasteringVoice->GetChannelMask(&XAudio2::ChannelMask); hr = XAudio2::MasteringVoice->GetChannelMask(&XAudio2::ChannelMask);
@@ -807,6 +693,7 @@ bool AudioBackendXAudio2::Base_Init()
} }
#else #else
XAudio2::Channels = 2; XAudio2::Channels = 2;
XAudio2::ChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
#endif #endif
LOG(Info, "XAudio2: {0} channels at {1} kHz", XAudio2::Channels, details.InputSampleRate / 1000.0f); LOG(Info, "XAudio2: {0} channels at {1} kHz", XAudio2::Channels, details.InputSampleRate / 1000.0f);
@@ -821,7 +708,6 @@ bool AudioBackendXAudio2::Base_Init()
void AudioBackendXAudio2::Base_Update() void AudioBackendXAudio2::Base_Update()
{ {
// Update dirty voices // Update dirty voices
const auto listener = XAudio2::GetListener();
float outputMatrix[MAX_CHANNELS_MATRIX_SIZE]; float outputMatrix[MAX_CHANNELS_MATRIX_SIZE];
for (int32 i = 0; i < XAudio2::Sources.Count(); i++) for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
{ {
@@ -829,7 +715,7 @@ void AudioBackendXAudio2::Base_Update()
if (source.IsFree() || !(source.IsDirty || XAudio2::ForceDirty)) if (source.IsFree() || !(source.IsDirty || XAudio2::ForceDirty))
continue; continue;
auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, *listener, source, XAudio2::Channels); auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, XAudio2::Listener, source, XAudio2::Channels);
mix.VolumeIntoChannels(); mix.VolumeIntoChannels();
AudioBackendTools::MapChannels(source.Channels, XAudio2::Channels, mix.Channels, outputMatrix); AudioBackendTools::MapChannels(source.Channels, XAudio2::Channels, mix.Channels, outputMatrix);

View File

@@ -12,37 +12,33 @@
class AudioBackendXAudio2 : public AudioBackend class AudioBackendXAudio2 : public AudioBackend
{ {
public: public:
// [AudioBackend] // [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override; void Listener_Reset() override;
void Listener_OnRemove(AudioListener* listener) override; void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_VelocityChanged(AudioListener* listener) override; void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_ReinitializeAll() override; void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override; uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_OnRemove(AudioSource* source) override; void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(AudioSource* source) override; void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(AudioSource* source) override; void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(AudioSource* source) override; void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(AudioSource* source) override; void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(AudioSource* source) override; void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(AudioSource* source) override; void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(AudioSource* source) override; void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_ClipLoaded(AudioSource* source) override; void Source_Play(uint32 sourceID) override;
void Source_Cleanup(AudioSource* source) override; void Source_Pause(uint32 sourceID) override;
void Source_Play(AudioSource* source) override; void Source_Stop(uint32 sourceID) override;
void Source_Pause(AudioSource* source) override; void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
void Source_Stop(AudioSource* source) override; float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override; void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override; void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override; void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override; void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override; void Source_DequeueProcessedBuffers(uint32 sourceID) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Buffer_Create() override; uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override; void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override; void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override; const Char* Base_Name() override;
FeatureFlags Base_Features() override; FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override; void Base_OnActiveDeviceChanged() override;

View File

@@ -536,6 +536,14 @@ void Asset::CancelStreaming()
#if USE_EDITOR #if USE_EDITOR
void Asset::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Fallback to the old API
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
GetReferences(assets);
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
}
void Asset::GetReferences(Array<Guid>& output) const void Asset::GetReferences(Array<Guid>& output) const
{ {
// No refs by default // No refs by default
@@ -544,7 +552,8 @@ void Asset::GetReferences(Array<Guid>& output) const
Array<Guid> Asset::GetReferences() const Array<Guid> Asset::GetReferences() const
{ {
Array<Guid> result; Array<Guid> result;
GetReferences(result); Array<String> files;
GetReferences(result, files);
return result; return result;
} }

View File

@@ -179,10 +179,13 @@ public:
/// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method, /// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method,
/// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API. /// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API.
/// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded. /// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded.
/// Also the output data may have duplicated asset ids or even invalid ids (Guid::Empty). /// Also, the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// </remarks> /// </remarks>
/// <param name="output">The output collection of the asset ids referenced by this asset.</param> /// <param name="assets">The output collection of the asset ids referenced by this asset.</param>
virtual void GetReferences(Array<Guid, HeapAllocation>& output) const; /// <param name="files">The output list of file paths referenced by this asset. Files might come from project Content folder (relative path is preserved in cooked game), or external location (copied into Content root folder of cooked game).</param>
virtual void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const;
// [Deprecated in v1.9]
DEPRECATED virtual void GetReferences(Array<Guid, HeapAllocation>& output) const;
/// <summary> /// <summary>
/// Gets the asset references. Supported only in Editor. /// Gets the asset references. Supported only in Editor.
@@ -191,7 +194,7 @@ public:
/// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method, /// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method,
/// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API. /// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API.
/// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded. /// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded.
/// Also the output data may have duplicated asset ids or even invalid ids (Guid::Empty). /// Also, the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// </remarks> /// </remarks>
/// <returns>The collection of the asset ids referenced by this asset.</returns> /// <returns>The collection of the asset ids referenced by this asset.</returns>
API_FUNCTION() Array<Guid, HeapAllocation> GetReferences() const; API_FUNCTION() Array<Guid, HeapAllocation> GetReferences() const;

View File

@@ -222,12 +222,10 @@ void AnimationGraph::FindDependencies(AnimGraphBase* graph)
} }
} }
void AnimationGraph::GetReferences(Array<Guid>& output) const void AnimationGraph::GetReferences(Array<Guid>& assets, Array<String>& files) const
{ {
// Base BinaryAsset::GetReferences(assets, files);
BinaryAsset::GetReferences(output); Graph.GetReferences(assets);
Graph.GetReferences(output);
} }
#endif #endif

View File

@@ -64,7 +64,7 @@ private:
public: public:
// [BinaryAsset] // [BinaryAsset]
#if USE_EDITOR #if USE_EDITOR
void GetReferences(Array<Guid>& output) const override; void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif #endif
protected: protected:

View File

@@ -45,3 +45,13 @@ MaterialInstance* MaterialBase::CreateVirtualInstance()
instance->SetBaseMaterial(this); instance->SetBaseMaterial(this);
return instance; return instance;
} }
#if USE_EDITOR
void MaterialBase::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
BinaryAsset::GetReferences(assets, files);
Params.GetReferences(assets);
}
#endif

View File

@@ -25,7 +25,7 @@ public:
Action ParamsChanged; Action ParamsChanged;
/// <summary> /// <summary>
/// Returns true if material is an material instance. /// Returns true if material is a material instance.
/// </summary> /// </summary>
virtual bool IsMaterialInstance() const = 0; virtual bool IsMaterialInstance() const = 0;
@@ -77,12 +77,6 @@ public:
public: public:
// [BinaryAsset] // [BinaryAsset]
#if USE_EDITOR #if USE_EDITOR
void GetReferences(Array<Guid>& output) const override void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
{
// Base
BinaryAsset::GetReferences(output);
Params.GetReferences(output);
}
#endif #endif
}; };

Some files were not shown because too many files have changed in this diff Show More