From 231ab345a038912fef02110e22396568c190cca4 Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Mon, 16 Nov 2015 21:17:46 +0000 Subject: [PATCH 01/16] Merge branch 'master' of https://github.com/RHY3756547/FSOMonoGame into develop Conflicts: NuGetPackages/MonoGame.Framework.Linux.nuspec NuGetPackages/MonoGame.Framework.WindowsGL.nuspec NuGetPackages/MonoGame.Framework.WindowsUAP.nuspec --- MonoGame.Framework/Threading.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/MonoGame.Framework/Threading.cs b/MonoGame.Framework/Threading.cs index b672c01bc27..b34656a6af9 100644 --- a/MonoGame.Framework/Threading.cs +++ b/MonoGame.Framework/Threading.cs @@ -195,7 +195,19 @@ internal static void BlockOnUIThread(Action action) lock (BackgroundContext) { // Make the context current on this thread - BackgroundContext.MakeCurrent(WindowInfo); + bool success = false; + while (!success) { + try { + BackgroundContext.MakeCurrent(WindowInfo); + success = true; + } + catch (GraphicsContextException ex) + { + //hack to circumvent a mystery error where the gc thread fails to capture the gfx context. + Console.WriteLine("Failed to get context. Trying again..."); + Thread.Sleep(1000); + } + } // Execute the action action(); // Must flush the GL calls so the texture is ready for the main context to use From b088e01eb641b88d05d55ef9af6a1a8a44162182 Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Sun, 3 Apr 2016 13:12:50 +0300 Subject: [PATCH 02/16] Implement DynamicSoundEffectInstance --- Build/Projects/MonoGame.Framework.definition | 9 + Build/Projects/MonoGame.Tests.definition | 2 + .../Android/AndroidGamePlatform.cs | 2 + .../DynamicSoundEffectInstance.OpenAL.cs | 186 +++++++++ .../DynamicSoundEffectInstance.XAudio.cs | 90 +++++ .../Audio/DynamicSoundEffectInstance.cs | 297 +++++++++++--- MonoGame.Framework/Audio/SoundEffect.cs | 4 + .../Audio/SoundEffectInstance.OpenAL.cs | 2 +- .../Audio/SoundEffectInstance.cs | 15 +- .../Audio/SoundEffectInstancePool.cs | 12 +- .../Desktop/OpenTKGamePlatform.cs | 4 + MonoGame.Framework/iOS/iOSGamePlatform.cs | 1 + .../Audio/DynamicSoundEffectInstanceTest.cs | 366 ++++++++++++++++++ Test/MonoGame.Tests.XNA.csproj | 1 + 14 files changed, 917 insertions(+), 74 deletions(-) create mode 100644 MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs create mode 100644 MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs create mode 100644 Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs diff --git a/Build/Projects/MonoGame.Framework.definition b/Build/Projects/MonoGame.Framework.definition index 2f41d93ca96..a09583997a4 100644 --- a/Build/Projects/MonoGame.Framework.definition +++ b/Build/Projects/MonoGame.Framework.definition @@ -262,6 +262,15 @@ Linux,WindowsGL,Angle,Android,Ouya + + OpenALAudio,XAudioAudio + + + OpenALAudio + + + XAudioAudio + Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,Web,tvOS diff --git a/Build/Projects/MonoGame.Tests.definition b/Build/Projects/MonoGame.Tests.definition index 4f12279e114..7c27af50cca 100644 --- a/Build/Projects/MonoGame.Tests.definition +++ b/Build/Projects/MonoGame.Tests.definition @@ -118,6 +118,8 @@ + + diff --git a/MonoGame.Framework/Android/AndroidGamePlatform.cs b/MonoGame.Framework/Android/AndroidGamePlatform.cs index 6f8e94b4675..cc8af36e268 100644 --- a/MonoGame.Framework/Android/AndroidGamePlatform.cs +++ b/MonoGame.Framework/Android/AndroidGamePlatform.cs @@ -71,6 +71,8 @@ public override bool BeforeUpdate(GameTime gameTime) Game.DoInitialize(); _initialized = true; } + + DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); return true; } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs new file mode 100644 index 00000000000..b329ea28221 --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using OpenTK.Audio.OpenAL; + +namespace Microsoft.Xna.Framework.Audio +{ + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance + { + private Queue _queuedBuffers; + private ALFormat _format; + + private void PlatformCreate() + { + _format = _channels == AudioChannels.Mono ? ALFormat.Mono16 : ALFormat.Stereo16; + InitializeSound(); + + SourceId = controller.ReserveSource(); + HasSourceId = true; + + _queuedBuffers = new Queue(); + } + + private int PlatformGetPendingBufferCount() + { + return _queuedBuffers.Count; + } + + private void PlatformPlay() + { + AL.GetError(); + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to play the source."); + + DynamicSoundEffectInstanceManager.AddInstance(this); + } + + private void PlatformPause() + { + AL.GetError(); + AL.SourcePause(SourceId); + ALHelper.CheckError("Failed to pause the source."); + } + + private void PlatformResume() + { + AL.GetError(); + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to play the source."); + } + + private void PlatformStop() + { + DynamicSoundEffectInstanceManager.RemoveInstance(this); + + AL.GetError(); + AL.SourceStop(SourceId); + ALHelper.CheckError("Failed to stop the source."); + + // Remove all queued buffers + AL.BindBufferToSource(SourceId, 0); + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + } + + private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) + { + // Get a buffer + OALSoundBuffer oalBuffer = new OALSoundBuffer(); + + // Bind the data + if (offset == 0) + { + oalBuffer.BindDataBuffer(buffer, _format, count, _sampleRate); + } + else + { + // BindDataBuffer does not support offset + var offsetBuffer = new byte[count]; + Array.Copy(buffer, offset, offsetBuffer, 0, count); + oalBuffer.BindDataBuffer(offsetBuffer, _format, count, _sampleRate); + } + + // Queue the buffer + AL.SourceQueueBuffer(SourceId, oalBuffer.OpenALDataBuffer); + ALHelper.CheckError(); + _queuedBuffers.Enqueue(oalBuffer); + + // If the source has run out of buffers, restart it + if (_state == SoundState.Playing) + { + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to resume source playback."); + } + } + + private void PlatformDispose(bool disposing) + { + // Stop the source and bind null buffer so that it can be recycled + AL.GetError(); + if (AL.IsSource(SourceId)) + { + AL.SourceStop(SourceId); + AL.BindBufferToSource(SourceId, 0); + ALHelper.CheckError("Failed to stop the source."); + controller.RecycleSource(SourceId); + } + + if (disposing) + { + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + + DynamicSoundEffectInstanceManager.RemoveInstance(this); + } + } + + internal void UpdateQueue() + { + // Get the completed buffers + AL.GetError(); + int numBuffers; + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out numBuffers); + ALHelper.CheckError("Failed to get processed buffer count."); + + // Unqueue them + if (numBuffers > 0) + { + AL.SourceUnqueueBuffers(SourceId, numBuffers); + ALHelper.CheckError("Failed to unqueue buffers."); + for (int i = 0; i < numBuffers; i++) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + } + + // Only raise the event if a buffer was removed + if (numBuffers > 0) + CheckBufferCount(); + } + } + + /// + /// Handles the buffer events of all DynamicSoundEffectInstance instances. + /// + internal static class DynamicSoundEffectInstanceManager + { + private static List _playingInstances; + + static DynamicSoundEffectInstanceManager() + { + _playingInstances = new List(); + } + + public static void AddInstance(DynamicSoundEffectInstance instance) + { + _playingInstances.Add(instance); + } + + public static void RemoveInstance(DynamicSoundEffectInstance instance) + { + _playingInstances.Remove(instance); + } + + /// + /// Updates buffer queues of the currently playing instances. + /// + /// + /// OpenAL does not implement callbacks as XAudio does, so this must be called periodically. + /// + public static void UpdatePlayingInstances() + { + foreach (var instance in _playingInstances) + { + instance.UpdateQueue(); + } + } + } +} diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs new file mode 100644 index 00000000000..dc180be8f06 --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using SharpDX; +using SharpDX.Multimedia; +using SharpDX.XAudio2; + +namespace Microsoft.Xna.Framework.Audio +{ + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance + { + private Queue _queuedBuffers; + + private void PlatformCreate() + { + _format = new WaveFormat(_sampleRate, (int)_channels); + _voice = new SourceVoice(SoundEffect.Device, _format, true); + _voice.BufferEnd += OnBufferEnd; + _queuedBuffers = new Queue(); + } + + private int PlatformGetPendingBufferCount() + { + return _queuedBuffers.Count; + } + + private void PlatformPlay() + { + _voice.Start(); + } + + private void PlatformPause() + { + _voice.Stop(); + } + + private void PlatformResume() + { + _voice.Start(); + } + + private void PlatformStop() + { + _voice.Stop(); + + // Dequeue all the submitted buffers + _voice.FlushSourceBuffers(); + + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + } + } + + private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) + { + var stream = DataStream.Create(buffer, true, false, offset, true); + var audioBuffer = new AudioBuffer(stream); + audioBuffer.AudioBytes = count; + + _voice.SubmitSourceBuffer(audioBuffer, null); + _queuedBuffers.Enqueue(audioBuffer); + } + + private void PlatformDispose(bool disposing) + { + if (disposing) + { + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + } + } + // _voice is disposed by SoundEffectInstance.PlatformDispose + } + + private void OnBufferEnd(IntPtr obj) + { + // Release the buffer + if (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + } + + CheckBufferCount(); + } + } +} diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index e0e435beef3..20f52de0b33 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -1,102 +1,279 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -#if MONOMAC -using MonoMac.OpenAL; -#else -using OpenTK.Audio.OpenAL; -#endif namespace Microsoft.Xna.Framework.Audio { - public sealed class DynamicSoundEffectInstance : SoundEffectInstance + /// + /// A for which the audio buffer is provided by the game at run time. + /// + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance { - private AudioChannels channels; - private int sampleRate; - private ALFormat format; - private bool looped; - private int pendingBufferCount; - // Events - public event EventHandler BufferNeeded; + #region Public Properties - internal void OnBufferNeeded(EventArgs args) + /// + /// This value has no effect on DynamicSoundEffectInstance. + /// It may not be set. + /// + public override bool IsLooped { - if (BufferNeeded != null) + get + { + return false; + } + + set { - BufferNeeded(this, args); + AssertNotDisposed(); + if (value == true) + throw new InvalidOperationException("IsLooped cannot be set true. Submit looped audio data to implement looping."); } } - public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) + public override SoundState State { - this.sampleRate = sampleRate; - this.channels = channels; - switch (channels) + get { - case AudioChannels.Mono: - this.format = ALFormat.Mono16; - break; - case AudioChannels.Stereo: - this.format = ALFormat.Stereo16; - break; - default: - break; - } - } - - /* - public TimeSpan GetSampleDuration(int sizeInBytes) - { - throw new NotImplementedException(); + AssertNotDisposed(); + return _state; + } } - public int GetSampleSizeInBytes(TimeSpan duration) + /// + /// Returns the number of audio buffers queued for playback. + /// + public int PendingBufferCount { - throw new NotImplementedException(); + get + { + AssertNotDisposed(); + return PlatformGetPendingBufferCount(); + } } - */ - public override void Play() + /// + /// The event that occurs when the number of queued audio buffers is less than or equal to 2. + /// + /// + /// This event may occur when is called or during playback when a buffer is completed. + /// + public event EventHandler BufferNeeded; + + #endregion + + private const int TargetPendingBufferCount = 3; + private int _sampleRate; + private AudioChannels _channels; + private SoundState _state; + + #region Public Constructor + + /// Sample rate, in Hertz (Hz). + /// Number of channels (mono or stereo). + public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) { - throw new NotImplementedException(); + if ((sampleRate < 8000) || (sampleRate > 48000)) + throw new ArgumentOutOfRangeException(nameof(sampleRate)); + if ((channels != AudioChannels.Mono) && (channels != AudioChannels.Stereo)) + throw new ArgumentOutOfRangeException(nameof(channels)); + + _sampleRate = sampleRate; + _channels = channels; + _state = SoundState.Stopped; + PlatformCreate(); + + // This instance is added to the pool so that its volume reflects master volume changes + // and it contributes to the playing instances limit, but the source/voice is not owned by the pool. + _isPooled = false; + _isDynamic = true; } - public void SubmitBuffer(byte[] buffer) + #endregion + + #region Public Functions + + /// + /// Returns the duration of an audio buffer of the specified size, based on the settings of this instance. + /// + /// Size of the buffer, in bytes. + /// The playback length of the buffer. + public TimeSpan GetSampleDuration(int sizeInBytes) { - this.SubmitBuffer(buffer, 0, buffer.Length); + AssertNotDisposed(); + return SoundEffect.GetSampleDuration(sizeInBytes, _sampleRate, _channels); } - public void SubmitBuffer(byte[] buffer, int offset, int count) + /// + /// Returns the size, in bytes, of a buffer of the specified duration, based on the settings of this instance. + /// + /// The playback length of the buffer. + /// The data size of the buffer, in bytes. + public int GetSampleSizeInBytes(TimeSpan duration) { - BindDataBuffer(buffer, format, count, sampleRate); + AssertNotDisposed(); + return SoundEffect.GetSampleSizeInBytes(duration, _sampleRate, _channels); } - public override bool IsLooped + /// + /// Plays or resumes the DynamicSoundEffectInstance. + /// + public override void Play() { - get - { - return looped; - } + AssertNotDisposed(); - set + if (_state != SoundState.Playing) { - looped = value; + // Ensure that the volume reflects master volume, which is done by the setter. + Volume = Volume; + + // Add the instance to the pool + if (!SoundEffectInstancePool.SoundsAvailable) + throw new InstancePlayLimitException(); + SoundEffectInstancePool.Remove(this); + + PlatformPlay(); + _state = SoundState.Playing; + + CheckBufferCount(); } } - public int PendingBufferCount + /// + /// Pauses playback of the DynamicSoundEffectInstance. + /// + public override void Pause() { - get + AssertNotDisposed(); + PlatformPause(); + _state = SoundState.Paused; + } + + /// + /// Resumes playback of the DynamicSoundEffectInstance. + /// + public override void Resume() + { + AssertNotDisposed(); + + if (_state != SoundState.Playing) { - return pendingBufferCount; + Volume = Volume; + + // Add the instance to the pool + if (!SoundEffectInstancePool.SoundsAvailable) + throw new InstancePlayLimitException(); + SoundEffectInstancePool.Remove(this); } - private set + + PlatformResume(); + _state = SoundState.Playing; + } + + /// + /// Immediately stops playing the DynamicSoundEffectInstance. + /// + /// + /// Calling this also releases all queued buffers. + /// + public override void Stop() + { + Stop(true); + } + + /// + /// Stops playing the DynamicSoundEffectInstance. + /// If the parameter is false, this call has no effect. + /// + /// + /// Calling this also releases all queued buffers. + /// + /// When set to false, this call has no effect. + public override void Stop(bool immediate) + { + AssertNotDisposed(); + + if (immediate) { - pendingBufferCount = value; + PlatformStop(); + _state = SoundState.Stopped; + + SoundEffectInstancePool.Add(this); } } - } + /// + /// Queues an audio buffer for playback. + /// + /// + /// The buffer length must conform to alignment requirements for the audio format. + /// + /// The buffer containing PCM audio data. + public void SubmitBuffer(byte[] buffer) + { + AssertNotDisposed(); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer may not be empty."); + + // Ensure that the buffer length matches alignment. + // The data must be 16-bit, so the length is a multiple of 2 (mono) or 4 (stereo). + var sampleSize = 2 * (int)_channels; + if (buffer.Length % sampleSize != 0) + throw new ArgumentException("Buffer length does not match format alignment."); + + SubmitBuffer(buffer, 0, buffer.Length); + } + + /// + /// Queues an audio buffer for playback. + /// + /// + /// The buffer length must conform to alignment requirements for the audio format. + /// + /// The buffer containing PCM audio data. + /// The starting position of audio data. + /// The amount of bytes to use. + public void SubmitBuffer(byte[] buffer, int offset, int count) + { + AssertNotDisposed(); + + if ((buffer == null) || (buffer.Length == 0)) + throw new ArgumentException("Buffer may not be null or empty."); + if (count <= 0) + throw new ArgumentException("Number of bytes must be greater than zero."); + if ((offset + count) > buffer.Length) + throw new ArgumentException("Buffer is shorter than the specified number of bytes from the offset."); + + // Ensure that the buffer length and start position match alignment. + var sampleSize = 2 * (int)_channels; + if (count % sampleSize != 0) + throw new ArgumentException("Number of bytes does not match format alignment."); + if (offset % sampleSize != 0) + throw new ArgumentException("Offset into the buffer does not match format alignment."); + PlatformSubmitBuffer(buffer, offset, count); + } + + #endregion + + #region Nonpublic Functions + + private void AssertNotDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(null); + } + + protected override void Dispose(bool disposing) + { + PlatformDispose(disposing); + base.Dispose(disposing); + } + + private void CheckBufferCount() + { + if ((PendingBufferCount < TargetPendingBufferCount) && (_state == SoundState.Playing) && (BufferNeeded != null)) + BufferNeeded(this, EventArgs.Empty); + } + + #endregion + } } diff --git a/MonoGame.Framework/Audio/SoundEffect.cs b/MonoGame.Framework/Audio/SoundEffect.cs index cb89c484e19..89959fe8508 100644 --- a/MonoGame.Framework/Audio/SoundEffect.cs +++ b/MonoGame.Framework/Audio/SoundEffect.cs @@ -128,6 +128,8 @@ public static TimeSpan GetSampleDuration(int sizeInBytes, int sampleRate, AudioC { if (sampleRate < 8000 || sampleRate > 48000) throw new ArgumentOutOfRangeException(); + if (sizeInBytes < 0) + throw new ArgumentException(); // Reference: http://social.msdn.microsoft.com/Forums/windows/en-US/5a92be69-3b4e-4d92-b1d2-141ef0a50c91/how-to-calculate-duration-of-wave-file-from-its-size?forum=winforms var numChannels = (int)channels; @@ -150,6 +152,8 @@ public static int GetSampleSizeInBytes(TimeSpan duration, int sampleRate, AudioC { if (sampleRate < 8000 || sampleRate > 48000) throw new ArgumentOutOfRangeException(); + if (duration < TimeSpan.Zero) + throw new ArgumentOutOfRangeException(); // Reference: http://social.msdn.microsoft.com/Forums/windows/en-US/5a92be69-3b4e-4d92-b1d2-141ef0a50c91/how-to-calculate-duration-of-wave-file-from-its-size?forum=winforms diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs index de0d328cccd..5f79121025f 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs @@ -21,7 +21,7 @@ public partial class SoundEffectInstance : IDisposable internal int SourceId; int pauseCount; - private OpenALSoundController controller; + internal OpenALSoundController controller; internal bool HasSourceId = false; diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.cs b/MonoGame.Framework/Audio/SoundEffectInstance.cs index f237d39e1cc..c91abc2c57a 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.cs @@ -15,6 +15,7 @@ public partial class SoundEffectInstance : IDisposable private bool _isDisposed = false; internal bool _isPooled = true; internal bool _isXAct; + internal bool _isDynamic; internal SoundEffect _effect; private float _pan; private float _volume; @@ -22,7 +23,7 @@ public partial class SoundEffectInstance : IDisposable /// Enables or Disables whether the SoundEffectInstance should repeat after playback. /// This value has no effect on an already playing sound. - public bool IsLooped + public virtual bool IsLooped { get { return PlatformGetIsLooped(); } set { PlatformSetIsLooped(value); } @@ -83,7 +84,7 @@ public float Volume } /// Gets the SoundEffectInstance's current playback state. - public SoundState State { get { return PlatformGetState(); } } + public virtual SoundState State { get { return PlatformGetState(); } } /// Indicates whether the object is disposed. public bool IsDisposed { get { return _isDisposed; } } @@ -129,14 +130,14 @@ public void Apply3D(AudioListener[] listeners, AudioEmitter emitter) /// Pauses playback of a SoundEffectInstance. /// Paused instances can be resumed with SoundEffectInstance.Play() or SoundEffectInstance.Resume(). - public void Pause() + public virtual void Pause() { PlatformPause(); } /// Plays or resumes a SoundEffectInstance. /// Throws an exception if more sounds are playing than the platform allows. - public void Play() + public virtual void Play() { if (State == SoundState.Playing) return; @@ -161,13 +162,13 @@ public void Play() /// Resumes playback for a SoundEffectInstance. /// Only has effect on a SoundEffectInstance in a paused state. - public void Resume() + public virtual void Resume() { PlatformResume(); } /// Immediately stops playing a SoundEffectInstance. - public void Stop() + public virtual void Stop() { PlatformStop(true); } @@ -175,7 +176,7 @@ public void Stop() /// Stops playing a SoundEffectInstance, either immediately or as authored. /// Determined whether the sound stops immediately, or after playing its release phase and/or transitions. /// Stopping a sound with the immediate argument set to false will allow it to play any release phases, such as fade, before coming to a stop. - public void Stop(bool immediate) + public virtual void Stop(bool immediate) { PlatformStop(immediate); } diff --git a/MonoGame.Framework/Audio/SoundEffectInstancePool.cs b/MonoGame.Framework/Audio/SoundEffectInstancePool.cs index e15940a264a..08407c4ad73 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstancePool.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstancePool.cs @@ -105,12 +105,12 @@ internal static void Update() // Cleanup instances which have finished playing. for (var x = 0; x < _playingInstances.Count;) { - inst = _playingInstances[x]; - - if (inst.State == SoundState.Stopped || inst.IsDisposed || inst._effect == null) - { - Add(inst); - continue; + inst = _playingInstances[x]; + + if (inst.IsDisposed || inst.State == SoundState.Stopped || (inst._effect == null && !inst._isDynamic)) + { + Add(inst); + continue; } x++; diff --git a/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs b/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs index 91e7cddd99d..9ec142d4562 100644 --- a/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs +++ b/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs @@ -210,6 +210,10 @@ public override bool BeforeUpdate(GameTime gameTime) // Update our OpenAL sound buffer pools if (soundControllerInstance != null) soundControllerInstance.Update(); + + // Update DynamicSoundEffectInstance instances + DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); + return true; } diff --git a/MonoGame.Framework/iOS/iOSGamePlatform.cs b/MonoGame.Framework/iOS/iOSGamePlatform.cs index 9a5c240397e..23e414cfdd7 100644 --- a/MonoGame.Framework/iOS/iOSGamePlatform.cs +++ b/MonoGame.Framework/iOS/iOSGamePlatform.cs @@ -246,6 +246,7 @@ public override bool BeforeDraw(GameTime gameTime) { // Update our OpenAL sound buffer pools soundControllerInstance.Update(); + DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); if (IsPlayingVideo) return false; diff --git a/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs new file mode 100644 index 00000000000..c42177ae273 --- /dev/null +++ b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs @@ -0,0 +1,366 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using NUnit.Framework; + +namespace MonoGame.Tests.Audio +{ + class DynamicSoundEffectInstanceTest + { + [SetUp] + public void SetUp() + { +#if XNA + FrameworkDispatcher.Update(); +#endif + } + + + + [Test] + public void BufferNeeded_DuringPlayback() + { + // XNA raises the event every time a buffer is consumed and there are less than two left. + + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += IncreaseBufferNeededEventCount; + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + SleepWhileDispatching(350); + Assert.AreEqual(3, _bufferNeededEventCount - previousEventCount); + } + } + + [Test] + public void BufferNeeded_Play_NoneSubmitted() + { + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += IncreaseBufferNeededEventCount; + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + SleepWhileDispatching(20); + Assert.AreEqual(1, _bufferNeededEventCount - previousEventCount); + } + } + + [Test] + public void BufferNeeded_Play_AlreadySubmitted() + { + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += IncreaseBufferNeededEventCount; + instance.SubmitBuffer(GenerateSineWave(440, 8000, 1, 0.1f)); + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + SleepWhileDispatching(20); + Assert.AreEqual(1, _bufferNeededEventCount - previousEventCount); + } + } + + static int _bufferNeededEventCount = 0; + private static void IncreaseBufferNeededEventCount(object sender, EventArgs e) + { + _bufferNeededEventCount++; + } + + [Test] + public void Ctor() + { + // Valid sample rates + var instance = new DynamicSoundEffectInstance(48000, AudioChannels.Stereo); + instance = new DynamicSoundEffectInstance(8000, AudioChannels.Stereo); + + // Invalid sample rates + Assert.Throws(() => { instance = new DynamicSoundEffectInstance(7999, AudioChannels.Stereo); }); + Assert.Throws(() => { instance = new DynamicSoundEffectInstance(48001, AudioChannels.Stereo); }); + + // Valid channel counts + instance = new DynamicSoundEffectInstance(44100, AudioChannels.Mono); + instance = new DynamicSoundEffectInstance(44100, AudioChannels.Stereo); + + // Invalid channel count + Assert.Throws(() => { instance = new DynamicSoundEffectInstance(44100, (AudioChannels)123); }); + } + + [Test] + public void GetSampleDuration() + { + var monoInstance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono); + var stereoInstance = new DynamicSoundEffectInstance(24000, AudioChannels.Stereo); + + // Zero length + Assert.AreEqual(TimeSpan.Zero, monoInstance.GetSampleDuration(0)); + Assert.AreEqual(TimeSpan.Zero, stereoInstance.GetSampleDuration(0)); + + // Nonzero length + Assert.AreEqual(TimeSpan.FromSeconds(1), monoInstance.GetSampleDuration(16000)); + Assert.AreEqual(TimeSpan.FromSeconds(1), stereoInstance.GetSampleDuration(96000)); + + // Length not aligned with format + Assert.AreEqual(TimeSpan.FromMilliseconds(1), stereoInstance.GetSampleDuration(97)); + + // Negative length + Assert.Throws(() => { monoInstance.GetSampleDuration(-1); }); + + // Disposed + monoInstance.Dispose(); + Assert.Throws(() => { monoInstance.GetSampleDuration(0); }); + } + + [Test] + public void GetSampleSizeInBytes() + { + var monoInstance = new DynamicSoundEffectInstance(48000, AudioChannels.Mono); + var stereoInstance = new DynamicSoundEffectInstance(22050, AudioChannels.Stereo); + + // Zero length + Assert.AreEqual(0, monoInstance.GetSampleSizeInBytes(TimeSpan.Zero)); + Assert.AreEqual(0, stereoInstance.GetSampleSizeInBytes(TimeSpan.Zero)); + + // Nonzero length + Assert.AreEqual(96000, monoInstance.GetSampleSizeInBytes(TimeSpan.FromSeconds(1))); + Assert.AreEqual(88200, stereoInstance.GetSampleSizeInBytes(TimeSpan.FromSeconds(1))); + + // Negative length + Assert.Throws(() => { monoInstance.GetSampleSizeInBytes(TimeSpan.FromSeconds(-1)); }); + + // Disposed + monoInstance.Dispose(); + Assert.Throws(() => { monoInstance.GetSampleSizeInBytes(TimeSpan.Zero); }); + } + + [Test] + public void IsLooped() + { + var instance = new DynamicSoundEffectInstance(24000, AudioChannels.Mono); + + // Always returns false and cannot be set true + Assert.IsFalse(instance.IsLooped); + instance.IsLooped = false; // Setting it to false does not throw, however + Assert.Throws(() => { instance.IsLooped = true; }); + + instance.Dispose(); + Assert.Throws(() => { instance.IsLooped = false; }); + } + + [Test] + public void PendingBufferCount() + { + var instance = new DynamicSoundEffectInstance(44100, AudioChannels.Stereo); + Assert.AreEqual(0, instance.PendingBufferCount); + + instance.SubmitBuffer(GenerateSineWave(440, 44100, 2, 1.0f)); + Assert.AreEqual(1, instance.PendingBufferCount); + + instance.Play(); + SleepWhileDispatching(1050); // Give it time to finish + Assert.AreEqual(0, instance.PendingBufferCount); + + // Throws ObjectDisposedException + instance.Dispose(); + Assert.Throws(() => { var a = instance.PendingBufferCount; }); + } + + [Test] + public void Playback() + { + using (var instance = new DynamicSoundEffectInstance(48000, AudioChannels.Mono)) + { + // Initially, the playback is stopped + Assert.AreEqual(SoundState.Stopped, instance.State); + + // Submitting a buffer will not change the state + instance.SubmitBuffer(GenerateSineWave(440, 48000, 1, 0.5f)); + Assert.AreEqual(SoundState.Stopped, instance.State); + + // Start playing + instance.Play(); + Assert.AreEqual(SoundState.Playing, instance.State); + + // While still playing, pause the playback + SleepWhileDispatching(300); + instance.Pause(); + Assert.AreEqual(SoundState.Paused, instance.State); + + // Let it continue and run out of buffers + instance.Resume(); + SleepWhileDispatching(300); + Assert.AreEqual(0, instance.PendingBufferCount); + Assert.AreEqual(SoundState.Playing, instance.State); + + // Submit a buffer and the playback should continue + instance.SubmitBuffer(GenerateSineWave(466, 48000, 1, 1.0f)); + Assert.AreEqual(SoundState.Playing, instance.State); + SleepWhileDispatching(500); + + // Stop immediately + Assert.AreEqual(SoundState.Playing, instance.State); + instance.Stop(); + SleepWhileDispatching(10); // XNA does not stop it until FrameworkDispatcher.Update is called + Assert.AreEqual(SoundState.Stopped, instance.State); + + // And then resume + instance.Resume(); + Assert.AreEqual(SoundState.Playing, instance.State); + } + } + + [Test] + public void Playback_Exceptions() + { + var instance = new DynamicSoundEffectInstance(16000, AudioChannels.Mono); + + instance.Dispose(); + Assert.Throws(() => { instance.Play(); }); + Assert.Throws(() => { instance.Pause(); }); + Assert.Throws(() => { instance.Resume(); }); + Assert.Throws(() => { instance.Stop(); }); + Assert.Throws(() => { instance.Stop(false); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[0]); }); + } + + [Test] + public void Stop_False() + { + // Calling Stop(false) has no effect + + using (var instance = new DynamicSoundEffectInstance(12000, AudioChannels.Mono)) + { + instance.Play(); + Assert.AreEqual(SoundState.Playing, instance.State); + + instance.Stop(false); + SleepWhileDispatching(20); + Assert.AreEqual(SoundState.Playing, instance.State); + } + } + + [Test] + public void Stop_RemovesBuffers() + { + using (var instance = new DynamicSoundEffectInstance(12000, AudioChannels.Mono)) + { + instance.SubmitBuffer(GenerateSineWave(440, 12000, 1, 0.1f)); + instance.SubmitBuffer(GenerateSineWave(440, 12000, 1, 0.1f)); + instance.SubmitBuffer(GenerateSineWave(440, 12000, 1, 0.1f)); + Assert.AreEqual(3, instance.PendingBufferCount); + + instance.Stop(); + SleepWhileDispatching(20); + Assert.AreEqual(0, instance.PendingBufferCount); + } + } + + [Test] + public void SubmitBuffer_ParameterValidation_SimpleOverload() + { + using (var instance = new DynamicSoundEffectInstance(16000, AudioChannels.Stereo)) + { + // Null or empty buffer - with different null behavior to the other overload + Assert.Throws(() => { instance.SubmitBuffer(null); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[0]); }); + + // Invalid alignment + Assert.Throws(() => { instance.SubmitBuffer(new byte[1]); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[2]); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[3]); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[13]); }); + + // Correct alignment and size + instance.SubmitBuffer(GenerateSineWave(440, 16000, 2, 0.1f)); + } + } + + [Test] + public void SubmitBuffer_ParameterValidation_ComplexOverload() + { + using (var instance = new DynamicSoundEffectInstance(16000, AudioChannels.Stereo)) + { + // Null or empty buffer - with different null behavior to the other overload + Assert.Throws(() => { instance.SubmitBuffer(null, 0, 4); }); + Assert.Throws(() => { instance.SubmitBuffer(new byte[0], 0, 4); }); + + var buffer = GenerateSineWave(440, 16000, 2, 0.5f); + + // Correct alignment + instance.SubmitBuffer(buffer, 0, 4); // One sample per channel + instance.SubmitBuffer(buffer, 1000, 1000); // 250 samples + + // Invalid alignment + Assert.Throws(() => { instance.SubmitBuffer(buffer, 0, 3); }); + Assert.Throws(() => { instance.SubmitBuffer(buffer, 1, 4); }); // Unaligned start position also throws + + // Invalid size + Assert.Throws(() => { instance.SubmitBuffer(buffer, 0, 0); }); + Assert.Throws(() => { instance.SubmitBuffer(buffer, 0, -1); }); + Assert.Throws(() => { instance.SubmitBuffer(buffer, 0, buffer.Length + 1); }); + Assert.Throws(() => { instance.SubmitBuffer(buffer, buffer.Length - 4, 8); }); + } + } + + + /// + /// Sleeps for the specified amount of time while calling FrameworkDispatcher.Update() every 10 ms. + /// + private static void SleepWhileDispatching(int ms) + { + int cycles = ms / 10; + for (int i = 0; i < cycles; i++) + { +#if XNA + FrameworkDispatcher.Update(); +#elif DESKTOPGL + DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); +#endif + System.Threading.Thread.Sleep(10); + } + } + + /// + /// Generates a audio buffer filled with a single frequency sine wave. + /// + /// Frequency in Hz. + /// Samples per second. + /// Number of channels. + /// Length in seconds. + /// An audio buffer of sufficient size, filled with sine wave. + private static byte[] GenerateSineWave(float frequency, int sampleRate, int channelCount, float length) + { + var sampleCount = (int)(sampleRate * length); + var samples = new short[sampleCount * channelCount]; + var onePerSampleRate = 1.0f / sampleRate; + + for (int i = 0; i < sampleCount; i++) + { + var sample = Math.Sin(2 * Math.PI * frequency * onePerSampleRate * i) * 0.2; + var sampleAsShort = (short)(sample * short.MaxValue); + + // Fill each channel + for (int channel = 0; channel < channelCount; channel++) + samples[i * channelCount + channel] = sampleAsShort; + } + + var byteArray = new byte[samples.Length * 2]; + for (int i = 0; i < samples.Length; i++) + { + var bytes = BitConverter.GetBytes(samples[i]); + byteArray[i * 2] = bytes[0]; + byteArray[i * 2 + 1] = bytes[1]; + } + + return byteArray; + } + } +} diff --git a/Test/MonoGame.Tests.XNA.csproj b/Test/MonoGame.Tests.XNA.csproj index 32b184753ee..3f39b692c72 100644 --- a/Test/MonoGame.Tests.XNA.csproj +++ b/Test/MonoGame.Tests.XNA.csproj @@ -167,6 +167,7 @@ + From 8c874cce389b77d3413acf98b35f9dc396559bae Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Sun, 3 Apr 2016 20:27:45 +0300 Subject: [PATCH 03/16] Do not use nameof --- MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index 20f52de0b33..eb4d96c5eff 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -71,9 +71,9 @@ public int PendingBufferCount public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) { if ((sampleRate < 8000) || (sampleRate > 48000)) - throw new ArgumentOutOfRangeException(nameof(sampleRate)); + throw new ArgumentOutOfRangeException("sampleRate"); if ((channels != AudioChannels.Mono) && (channels != AudioChannels.Stereo)) - throw new ArgumentOutOfRangeException(nameof(channels)); + throw new ArgumentOutOfRangeException("channels"); _sampleRate = sampleRate; _channels = channels; From 5f41cfb11c00a9c53af3491b208c7cccda62e871 Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Sun, 3 Apr 2016 21:44:43 +0300 Subject: [PATCH 04/16] Fix MonoMac usings --- .../Audio/DynamicSoundEffectInstance.OpenAL.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index b329ea28221..6184d42b603 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; -using System.Threading; +#if MONOMAC && PLATFORM_MACOS_LEGACY +using MonoMac.OpenAL; +#elif OPENAL using OpenTK.Audio.OpenAL; +#endif namespace Microsoft.Xna.Framework.Audio { From 9f8915e6671d97d0509d2d9cd432bcc604545e05 Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Fri, 8 Apr 2016 10:50:53 +0300 Subject: [PATCH 05/16] WeakReference in DynamicSoundEffectInstanceManager --- .../DynamicSoundEffectInstance.OpenAL.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index 6184d42b603..981c8d6f3b2 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -155,21 +155,29 @@ internal void UpdateQueue() /// internal static class DynamicSoundEffectInstanceManager { - private static List _playingInstances; + private static List _playingInstances; static DynamicSoundEffectInstanceManager() { - _playingInstances = new List(); + _playingInstances = new List(); } public static void AddInstance(DynamicSoundEffectInstance instance) { - _playingInstances.Add(instance); + var weakRef = new WeakReference(instance); + _playingInstances.Add(weakRef); } public static void RemoveInstance(DynamicSoundEffectInstance instance) { - _playingInstances.Remove(instance); + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].Target == instance) + { + _playingInstances.RemoveAt(i); + return; + } + } } /// @@ -180,9 +188,19 @@ public static void RemoveInstance(DynamicSoundEffectInstance instance) /// public static void UpdatePlayingInstances() { - foreach (var instance in _playingInstances) + for (int i = _playingInstances.Count - 1; i >= 0; i--) { - instance.UpdateQueue(); + if (_playingInstances[i].IsAlive) + { + var target = (DynamicSoundEffectInstance)_playingInstances[i].Target; + if (!target.IsDisposed) + target.UpdateQueue(); + } + else + { + // The instance has been disposed. + _playingInstances.RemoveAt(i); + } } } } From f6e36c37c6142fcce2ffc17f3dcb0bd202af2d88 Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Fri, 8 Apr 2016 21:34:04 +0300 Subject: [PATCH 06/16] Test DynamicSoundEffectInstance threading behavior --- .../Audio/DynamicSoundEffectInstanceTest.cs | 81 +++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs index c42177ae273..f8c1dc99f91 100644 --- a/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs +++ b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs @@ -3,6 +3,7 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.Threading; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using NUnit.Framework; @@ -28,7 +29,7 @@ public void BufferNeeded_DuringPlayback() using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) { - instance.BufferNeeded += IncreaseBufferNeededEventCount; + instance.BufferNeeded += BufferNeededEventHandler; instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.1f)); @@ -37,6 +38,53 @@ public void BufferNeeded_DuringPlayback() instance.Play(); SleepWhileDispatching(350); Assert.AreEqual(3, _bufferNeededEventCount - previousEventCount); + + // The event is raised on the same thread as FrameworkDispatcher.Update() is called. + Assert.AreEqual(Thread.CurrentThread.ManagedThreadId, _bufferNeededEventThread); + } + } + + [Test] + public void BufferNeeded_MultipleConsumed() + { + // Both buffers should be consumed by the time the event routine is called by XNA. + // This test verifies that each consumed buffer raises its own event. + + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += BufferNeededEventHandler; + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.05f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.05f)); + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + + Thread.Sleep(125); + SleepWhileDispatching(10); + + Assert.AreEqual(3, _bufferNeededEventCount - previousEventCount); + } + } + + [Test] + public void BufferNeeded_MoreThanThree() + { + // No events are raised when a buffer is consumed and there are more than 3 buffers submitted. + + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += BufferNeededEventHandler; + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.25f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.25f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.05f)); + instance.SubmitBuffer(GenerateSineWave(880, 8000, 1, 0.05f)); + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + + SleepWhileDispatching(300); + + Assert.AreEqual(0, _bufferNeededEventCount - previousEventCount); } } @@ -45,12 +93,15 @@ public void BufferNeeded_Play_NoneSubmitted() { using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) { - instance.BufferNeeded += IncreaseBufferNeededEventCount; + instance.BufferNeeded += BufferNeededEventHandler; var previousEventCount = _bufferNeededEventCount; instance.Play(); SleepWhileDispatching(20); Assert.AreEqual(1, _bufferNeededEventCount - previousEventCount); + + // The event is raised on the same thread as FrameworkDispatcher.Update() is called. + Assert.AreEqual(Thread.CurrentThread.ManagedThreadId, _bufferNeededEventThread); } } @@ -59,7 +110,7 @@ public void BufferNeeded_Play_AlreadySubmitted() { using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) { - instance.BufferNeeded += IncreaseBufferNeededEventCount; + instance.BufferNeeded += BufferNeededEventHandler; instance.SubmitBuffer(GenerateSineWave(440, 8000, 1, 0.1f)); var previousEventCount = _bufferNeededEventCount; @@ -69,10 +120,30 @@ public void BufferNeeded_Play_AlreadySubmitted() } } + [Test] + public void BufferNeeded_Play_NoDispatcherCalled() + { + // No event is raised if FrameworkDispatcher.Update() is not called. + + using (var instance = new DynamicSoundEffectInstance(8000, AudioChannels.Mono)) + { + instance.BufferNeeded += BufferNeededEventHandler; + + var previousEventCount = _bufferNeededEventCount; + instance.Play(); + + Thread.Sleep(20); + + Assert.AreEqual(0, _bufferNeededEventCount - previousEventCount); + } + } + static int _bufferNeededEventCount = 0; - private static void IncreaseBufferNeededEventCount(object sender, EventArgs e) + static int _bufferNeededEventThread = 0; + private static void BufferNeededEventHandler(object sender, EventArgs e) { _bufferNeededEventCount++; + _bufferNeededEventThread = Thread.CurrentThread.ManagedThreadId; } [Test] @@ -324,7 +395,7 @@ private static void SleepWhileDispatching(int ms) #elif DESKTOPGL DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); #endif - System.Threading.Thread.Sleep(10); + Thread.Sleep(10); } } From 6f8781d5351af1080cf667b9d26dcfc7aa9e313e Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Fri, 8 Apr 2016 21:52:37 +0300 Subject: [PATCH 07/16] Fix OpenAL version to pass new tests --- .../Audio/DynamicSoundEffectInstance.OpenAL.cs | 5 +++-- .../Audio/DynamicSoundEffectInstance.cs | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index 981c8d6f3b2..35d433c5a50 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -144,9 +144,10 @@ internal void UpdateQueue() } } - // Only raise the event if a buffer was removed - if (numBuffers > 0) + // Raise the event for each removed buffer, if needed + for (int i = 0; i < numBuffers; i++) CheckBufferCount(); + RaiseBufferNeeded(); } } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index eb4d96c5eff..44d3a5681aa 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -60,6 +60,7 @@ public int PendingBufferCount #endregion private const int TargetPendingBufferCount = 3; + private int _buffersNeeded; private int _sampleRate; private AudioChannels _channels; private SoundState _state; @@ -270,8 +271,21 @@ protected override void Dispose(bool disposing) private void CheckBufferCount() { - if ((PendingBufferCount < TargetPendingBufferCount) && (_state == SoundState.Playing) && (BufferNeeded != null)) - BufferNeeded(this, EventArgs.Empty); + if ((PendingBufferCount < TargetPendingBufferCount) && (_state == SoundState.Playing)) + _buffersNeeded++; + } + + private void RaiseBufferNeeded() + { + if (BufferNeeded != null) + { + for (var i = 0; i < _buffersNeeded; i++) + { + BufferNeeded(this, EventArgs.Empty); + } + } + + _buffersNeeded = 0; } #endregion From 95c9379dc4290453113230cf54651988c515a91a Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Fri, 8 Apr 2016 22:39:36 +0300 Subject: [PATCH 08/16] Fix XAudio version to pass the new tests --- .../Android/AndroidGamePlatform.cs | 2 - .../DynamicSoundEffectInstance.OpenAL.cs | 62 +---------------- .../DynamicSoundEffectInstance.XAudio.cs | 5 ++ .../Audio/DynamicSoundEffectInstance.cs | 68 ++++++++++++++++++- .../Desktop/OpenTKGamePlatform.cs | 3 - MonoGame.Framework/Game.cs | 1 + MonoGame.Framework/iOS/iOSGamePlatform.cs | 1 - .../Audio/DynamicSoundEffectInstanceTest.cs | 2 +- 8 files changed, 74 insertions(+), 70 deletions(-) diff --git a/MonoGame.Framework/Android/AndroidGamePlatform.cs b/MonoGame.Framework/Android/AndroidGamePlatform.cs index cc8af36e268..6f8e94b4675 100644 --- a/MonoGame.Framework/Android/AndroidGamePlatform.cs +++ b/MonoGame.Framework/Android/AndroidGamePlatform.cs @@ -71,8 +71,6 @@ public override bool BeforeUpdate(GameTime gameTime) Game.DoInitialize(); _initialized = true; } - - DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); return true; } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index 35d433c5a50..98c65080b75 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -34,8 +34,6 @@ private void PlatformPlay() AL.GetError(); AL.SourcePlay(SourceId); ALHelper.CheckError("Failed to play the source."); - - DynamicSoundEffectInstanceManager.AddInstance(this); } private void PlatformPause() @@ -54,8 +52,6 @@ private void PlatformResume() private void PlatformStop() { - DynamicSoundEffectInstanceManager.RemoveInstance(this); - AL.GetError(); AL.SourceStop(SourceId); ALHelper.CheckError("Failed to stop the source."); @@ -124,7 +120,7 @@ private void PlatformDispose(bool disposing) } } - internal void UpdateQueue() + private void PlatformUpdateQueue() { // Get the completed buffers AL.GetError(); @@ -147,62 +143,6 @@ internal void UpdateQueue() // Raise the event for each removed buffer, if needed for (int i = 0; i < numBuffers; i++) CheckBufferCount(); - RaiseBufferNeeded(); - } - } - - /// - /// Handles the buffer events of all DynamicSoundEffectInstance instances. - /// - internal static class DynamicSoundEffectInstanceManager - { - private static List _playingInstances; - - static DynamicSoundEffectInstanceManager() - { - _playingInstances = new List(); - } - - public static void AddInstance(DynamicSoundEffectInstance instance) - { - var weakRef = new WeakReference(instance); - _playingInstances.Add(weakRef); - } - - public static void RemoveInstance(DynamicSoundEffectInstance instance) - { - for (int i = _playingInstances.Count - 1; i >= 0; i--) - { - if (_playingInstances[i].Target == instance) - { - _playingInstances.RemoveAt(i); - return; - } - } - } - - /// - /// Updates buffer queues of the currently playing instances. - /// - /// - /// OpenAL does not implement callbacks as XAudio does, so this must be called periodically. - /// - public static void UpdatePlayingInstances() - { - for (int i = _playingInstances.Count - 1; i >= 0; i--) - { - if (_playingInstances[i].IsAlive) - { - var target = (DynamicSoundEffectInstance)_playingInstances[i].Target; - if (!target.IsDisposed) - target.UpdateQueue(); - } - else - { - // The instance has been disposed. - _playingInstances.RemoveAt(i); - } - } } } } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs index dc180be8f06..19d79eea7ba 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs @@ -62,6 +62,11 @@ private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) _queuedBuffers.Enqueue(audioBuffer); } + private void PlatformUpdateQueue() + { + // The XAudio implementation utilizes callbacks, so no work here. + } + private void PlatformDispose(bool disposing) { if (disposing) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index 44d3a5681aa..f183018a935 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Microsoft.Xna.Framework.Audio { @@ -134,6 +135,7 @@ public override void Play() _state = SoundState.Playing; CheckBufferCount(); + DynamicSoundEffectInstanceManager.AddInstance(this); } } @@ -193,6 +195,8 @@ public override void Stop(bool immediate) if (immediate) { + DynamicSoundEffectInstanceManager.RemoveInstance(this); + PlatformStop(); _state = SoundState.Stopped; @@ -275,11 +279,16 @@ private void CheckBufferCount() _buffersNeeded++; } - private void RaiseBufferNeeded() + internal void UpdateQueue() { + // Update the buffers + PlatformUpdateQueue(); + + // Raise the event if (BufferNeeded != null) { - for (var i = 0; i < _buffersNeeded; i++) + var eventCount = (_buffersNeeded < 3) ? _buffersNeeded : 3; + for (var i = 0; i < eventCount; i++) { BufferNeeded(this, EventArgs.Empty); } @@ -290,4 +299,59 @@ private void RaiseBufferNeeded() #endregion } + + /// + /// Handles the buffer events of all DynamicSoundEffectInstance instances. + /// + internal static class DynamicSoundEffectInstanceManager + { + private static List _playingInstances; + + static DynamicSoundEffectInstanceManager() + { + _playingInstances = new List(); + } + + public static void AddInstance(DynamicSoundEffectInstance instance) + { + var weakRef = new WeakReference(instance); + _playingInstances.Add(weakRef); + } + + public static void RemoveInstance(DynamicSoundEffectInstance instance) + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].Target == instance) + { + _playingInstances.RemoveAt(i); + return; + } + } + } + + /// + /// Updates buffer queues of the currently playing instances. + /// + /// + /// XNA posts events always on the main thread. + /// + public static void UpdatePlayingInstances() + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].IsAlive) + { + var target = (DynamicSoundEffectInstance)_playingInstances[i].Target; + if (!target.IsDisposed) + target.UpdateQueue(); + } + else + { + // The instance has been disposed. + _playingInstances.RemoveAt(i); + } + } + } + } } diff --git a/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs b/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs index 9ec142d4562..ca722f85508 100644 --- a/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs +++ b/MonoGame.Framework/Desktop/OpenTKGamePlatform.cs @@ -211,9 +211,6 @@ public override bool BeforeUpdate(GameTime gameTime) if (soundControllerInstance != null) soundControllerInstance.Update(); - // Update DynamicSoundEffectInstance instances - DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); - return true; } diff --git a/MonoGame.Framework/Game.cs b/MonoGame.Framework/Game.cs index 4642405ff71..011e85e834b 100644 --- a/MonoGame.Framework/Game.cs +++ b/MonoGame.Framework/Game.cs @@ -664,6 +664,7 @@ internal void DoUpdate(GameTime gameTime) // playing sounds to see if they've stopped, // and return them back to the pool if so. SoundEffectInstancePool.Update(); + DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); Update(gameTime); diff --git a/MonoGame.Framework/iOS/iOSGamePlatform.cs b/MonoGame.Framework/iOS/iOSGamePlatform.cs index 23e414cfdd7..9a5c240397e 100644 --- a/MonoGame.Framework/iOS/iOSGamePlatform.cs +++ b/MonoGame.Framework/iOS/iOSGamePlatform.cs @@ -246,7 +246,6 @@ public override bool BeforeDraw(GameTime gameTime) { // Update our OpenAL sound buffer pools soundControllerInstance.Update(); - DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); if (IsPlayingVideo) return false; diff --git a/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs index f8c1dc99f91..4b897497bff 100644 --- a/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs +++ b/Test/Framework/Audio/DynamicSoundEffectInstanceTest.cs @@ -392,7 +392,7 @@ private static void SleepWhileDispatching(int ms) { #if XNA FrameworkDispatcher.Update(); -#elif DESKTOPGL +#else DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); #endif Thread.Sleep(10); From c7025792255e86c765bf2e7872ca2183b48dc418 Mon Sep 17 00:00:00 2001 From: Petri Laarne Date: Sat, 9 Apr 2016 20:52:53 +0300 Subject: [PATCH 09/16] Move DynamicSoundEffectInstanceManager --- Build/Projects/MonoGame.Framework.definition | 3 + .../DynamicSoundEffectInstance.OpenAL.cs | 6 +- .../DynamicSoundEffectInstance.XAudio.cs | 6 +- .../Audio/DynamicSoundEffectInstance.cs | 61 ++---------------- .../DynamicSoundEffectInstanceManager.cs | 64 +++++++++++++++++++ MonoGame.Framework/Game.cs | 2 + 6 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs diff --git a/Build/Projects/MonoGame.Framework.definition b/Build/Projects/MonoGame.Framework.definition index a09583997a4..9aabcd6ee93 100644 --- a/Build/Projects/MonoGame.Framework.definition +++ b/Build/Projects/MonoGame.Framework.definition @@ -271,6 +271,9 @@ XAudioAudio + + OpenALAudio,XAudioAudio + Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,Web,tvOS diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index 98c65080b75..7722208e091 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -1,4 +1,8 @@ -using System; +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; using System.Collections.Generic; #if MONOMAC && PLATFORM_MACOS_LEGACY using MonoMac.OpenAL; diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs index 19d79eea7ba..35b7e9d3ed2 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs @@ -1,4 +1,8 @@ -using System; +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; using System.Collections.Generic; using SharpDX; using SharpDX.Multimedia; diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index f183018a935..6e6fc582c77 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -1,4 +1,8 @@ -using System; +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; using System.Collections.Generic; namespace Microsoft.Xna.Framework.Audio @@ -299,59 +303,4 @@ internal void UpdateQueue() #endregion } - - /// - /// Handles the buffer events of all DynamicSoundEffectInstance instances. - /// - internal static class DynamicSoundEffectInstanceManager - { - private static List _playingInstances; - - static DynamicSoundEffectInstanceManager() - { - _playingInstances = new List(); - } - - public static void AddInstance(DynamicSoundEffectInstance instance) - { - var weakRef = new WeakReference(instance); - _playingInstances.Add(weakRef); - } - - public static void RemoveInstance(DynamicSoundEffectInstance instance) - { - for (int i = _playingInstances.Count - 1; i >= 0; i--) - { - if (_playingInstances[i].Target == instance) - { - _playingInstances.RemoveAt(i); - return; - } - } - } - - /// - /// Updates buffer queues of the currently playing instances. - /// - /// - /// XNA posts events always on the main thread. - /// - public static void UpdatePlayingInstances() - { - for (int i = _playingInstances.Count - 1; i >= 0; i--) - { - if (_playingInstances[i].IsAlive) - { - var target = (DynamicSoundEffectInstance)_playingInstances[i].Target; - if (!target.IsDisposed) - target.UpdateQueue(); - } - else - { - // The instance has been disposed. - _playingInstances.RemoveAt(i); - } - } - } - } } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs new file mode 100644 index 00000000000..d8d3b8f0e2e --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs @@ -0,0 +1,64 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Xna.Framework.Audio +{ + /// + /// Handles the buffer events of all DynamicSoundEffectInstance instances. + /// + internal static class DynamicSoundEffectInstanceManager + { + private static List _playingInstances; + + static DynamicSoundEffectInstanceManager() + { + _playingInstances = new List(); + } + + public static void AddInstance(DynamicSoundEffectInstance instance) + { + var weakRef = new WeakReference(instance); + _playingInstances.Add(weakRef); + } + + public static void RemoveInstance(DynamicSoundEffectInstance instance) + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].Target == instance) + { + _playingInstances.RemoveAt(i); + return; + } + } + } + + /// + /// Updates buffer queues of the currently playing instances. + /// + /// + /// XNA posts events always on the main thread. + /// + public static void UpdatePlayingInstances() + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].IsAlive) + { + var target = (DynamicSoundEffectInstance)_playingInstances[i].Target; + if (!target.IsDisposed) + target.UpdateQueue(); + } + else + { + // The instance has been disposed. + _playingInstances.RemoveAt(i); + } + } + } + } +} diff --git a/MonoGame.Framework/Game.cs b/MonoGame.Framework/Game.cs index 011e85e834b..22d4f7ba3ab 100644 --- a/MonoGame.Framework/Game.cs +++ b/MonoGame.Framework/Game.cs @@ -664,7 +664,9 @@ internal void DoUpdate(GameTime gameTime) // playing sounds to see if they've stopped, // and return them back to the pool if so. SoundEffectInstancePool.Update(); +#if DIRECTX || OPENGL DynamicSoundEffectInstanceManager.UpdatePlayingInstances(); +#endif Update(gameTime); From a9a28386e9afaf11966762ea54c75115acab2ca7 Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Sat, 28 May 2016 16:38:46 +0100 Subject: [PATCH 10/16] [FSO] Fix some minor issues. --- MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs | 2 ++ MonoGame.Framework/SDL/SDL2.cs | 2 +- MonoGame.Framework/SDL/SDLGamePlatform.cs | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs index 7722208e091..52a847cc7f8 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -36,6 +36,8 @@ private int PlatformGetPendingBufferCount() private void PlatformPlay() { AL.GetError(); + AL.Source(SourceId, ALSourceb.Looping, IsLooped); + ALHelper.CheckError("Failed to set source loop state."); AL.SourcePlay(SourceId); ALHelper.CheckError("Failed to play the source."); } diff --git a/MonoGame.Framework/SDL/SDL2.cs b/MonoGame.Framework/SDL/SDL2.cs index 31e07ea8aa0..eec97260550 100644 --- a/MonoGame.Framework/SDL/SDL2.cs +++ b/MonoGame.Framework/SDL/SDL2.cs @@ -50,7 +50,7 @@ public enum EventType MouseWheel = 0x403, } - [StructLayout(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Explicit, Size = 56)] public struct Event { [FieldOffset(0)] public EventType Type; diff --git a/MonoGame.Framework/SDL/SDLGamePlatform.cs b/MonoGame.Framework/SDL/SDLGamePlatform.cs index 288d9b660ea..c6fb853a19a 100644 --- a/MonoGame.Framework/SDL/SDLGamePlatform.cs +++ b/MonoGame.Framework/SDL/SDLGamePlatform.cs @@ -108,7 +108,6 @@ public override void RunLoop() private void SdlRunLoop() { Sdl.Event ev; - while (Sdl.PollEvent(out ev) == 1) { if (ev.Type == Sdl.EventType.Quit) @@ -142,6 +141,8 @@ private void SdlRunLoop() if (text.Length == 0) continue; + text = text.Substring(0, 1); + foreach (var c in text) _view.CallTextInput(c); } From 8c54970bb57a348dac80b492fa1a0ed34c2976c6 Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Sun, 12 Jun 2016 22:37:36 +0100 Subject: [PATCH 11/16] Fix Dependencies --- ThirdParty/Dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ThirdParty/Dependencies b/ThirdParty/Dependencies index a62d80be574..49eb2a3e219 160000 --- a/ThirdParty/Dependencies +++ b/ThirdParty/Dependencies @@ -1 +1 @@ -Subproject commit a62d80be574ef74ee4196e3f7f1a40835634d857 +Subproject commit 49eb2a3e2190b09433a856508d94b14b649da473 From 1cbf6787155472af827b2c69656dfa68d7ee2fa4 Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Fri, 12 Aug 2016 19:54:08 +0100 Subject: [PATCH 12/16] Quick patch to fix x86 --- MonoGame.Framework/Audio/OpenAL.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/MonoGame.Framework/Audio/OpenAL.cs b/MonoGame.Framework/Audio/OpenAL.cs index 2191836f25c..da88491e1f7 100644 --- a/MonoGame.Framework/Audio/OpenAL.cs +++ b/MonoGame.Framework/Audio/OpenAL.cs @@ -544,10 +544,14 @@ public class EffectsExtension private delegate void alAuxiliaryEffectSlotiDelegate (uint slot, EfxEffecti type, uint effect); /* Filter API */ - private unsafe delegate void alGenFiltersDelegate (int n, [Out] uint* filters); - private delegate void alFilteriDelegate (uint fid, EfxFilteri param, int value); - private delegate void alFilterfDelegate (uint fid, EfxFilterf param, float value); - private unsafe delegate void alDeleteFiltersDelegate (int n, [In] uint* filters); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void alGenFiltersDelegate(int n, [Out] uint* filters); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void alFilteriDelegate(uint fid, EfxFilteri param, int value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void alFilterfDelegate(uint fid, EfxFilterf param, float value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void alDeleteFiltersDelegate(int n, [In] uint* filters); private alGenEffectsDelegate alGenEffects; From b226a43b7a1d5b913c6db7dbdf665f67d6396306 Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Sat, 20 Aug 2016 21:08:10 +0100 Subject: [PATCH 13/16] [FSO] Support DepthStencil inherit --- .../Graphics/RenderTarget2D.DirectX.cs | 6 ++++++ MonoGame.Framework/Graphics/RenderTarget2D.OpenGL.cs | 12 ++++++++++++ Tools/2MGFX/ShaderData.mojo.cs | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/MonoGame.Framework/Graphics/RenderTarget2D.DirectX.cs b/MonoGame.Framework/Graphics/RenderTarget2D.DirectX.cs index 7335339d8e3..6b85cc519c5 100644 --- a/MonoGame.Framework/Graphics/RenderTarget2D.DirectX.cs +++ b/MonoGame.Framework/Graphics/RenderTarget2D.DirectX.cs @@ -126,5 +126,11 @@ DepthStencilView IRenderTarget.GetDepthStencilView() GenerateIfRequired(); return _depthStencilView; } + + public void InheritDepthStencil(RenderTarget2D from) + { + if (from == null) _depthStencilView = GraphicsDevice._depthStencilView; + else _depthStencilView = from._depthStencilView; + } } } diff --git a/MonoGame.Framework/Graphics/RenderTarget2D.OpenGL.cs b/MonoGame.Framework/Graphics/RenderTarget2D.OpenGL.cs index 874c6771a8e..47f87da630f 100644 --- a/MonoGame.Framework/Graphics/RenderTarget2D.OpenGL.cs +++ b/MonoGame.Framework/Graphics/RenderTarget2D.OpenGL.cs @@ -67,5 +67,17 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + public void InheritDepthStencil(RenderTarget2D from) + { + if (from == null) + { + //must use other method + } else + { + ((IRenderTarget)this).GLDepthBuffer = ((IRenderTarget)from).GLDepthBuffer; + ((IRenderTarget)this).GLStencilBuffer = ((IRenderTarget)from).GLStencilBuffer; + } + } } } diff --git a/Tools/2MGFX/ShaderData.mojo.cs b/Tools/2MGFX/ShaderData.mojo.cs index 019e36a7c32..1e482f1ae1a 100644 --- a/Tools/2MGFX/ShaderData.mojo.cs +++ b/Tools/2MGFX/ShaderData.mojo.cs @@ -170,7 +170,7 @@ public static ShaderData CreateGLSL(byte[] byteCode, bool isVertexShader, List Date: Sun, 28 Aug 2016 04:50:50 +0100 Subject: [PATCH 14/16] Include TextEvent structures in iOS/Android. --- Build/Projects/MonoGame.Framework.definition | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/Projects/MonoGame.Framework.definition b/Build/Projects/MonoGame.Framework.definition index 7869e43b556..7e544dd77b1 100644 --- a/Build/Projects/MonoGame.Framework.definition +++ b/Build/Projects/MonoGame.Framework.definition @@ -343,7 +343,7 @@ Windows8,WindowsPhone81 - Angle,Linux,MacOS,Windows,WindowsGL,WindowsUniversal + Android,Angle,iOS,Linux,MacOS,Windows,WindowsGL,WindowsUniversal Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,WindowsPhone,tvOS From ad2528cbd5eebe6114d2c807029949e0b9cb817c Mon Sep 17 00:00:00 2001 From: RHY3756547 Date: Tue, 30 Aug 2016 04:43:26 +0100 Subject: [PATCH 15/16] Greatly improve GraphicsResource Dispose performance. --- MonoGame.Framework/Graphics/GraphicsDevice.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MonoGame.Framework/Graphics/GraphicsDevice.cs b/MonoGame.Framework/Graphics/GraphicsDevice.cs index 6b2af0b8559..dd31258c0c2 100644 --- a/MonoGame.Framework/Graphics/GraphicsDevice.cs +++ b/MonoGame.Framework/Graphics/GraphicsDevice.cs @@ -109,7 +109,7 @@ private bool PixelShaderDirty // Use WeakReference for the global resources list as we do not know when a resource // may be disposed and collected. We do not want to prevent a resource from being // collected by holding a strong reference to it in this list. - private readonly List _resources = new List(); + private readonly HashSet _resources = new HashSet(); // TODO Graphics Device events need implementing public event EventHandler DeviceLost; @@ -469,7 +469,7 @@ protected virtual void Dispose(bool disposing) // Dispose of all remaining graphics resources before disposing of the graphics device lock (_resourcesLock) { - foreach (var resource in _resources.ToArray()) + foreach (var resource in (new List(_resources)).ToArray()) { var target = resource.Target as IDisposable; if (target != null) @@ -560,7 +560,7 @@ internal void OnDeviceResetting() } // Remove references to resources that have been garbage collected. - _resources.RemoveAll(wr => !wr.IsAlive); + _resources.RemoveWhere(wr => !wr.IsAlive); } } From 8a4b47889891ad10b5634c2788433766337e643d Mon Sep 17 00:00:00 2001 From: Bengrs Date: Sun, 18 Dec 2016 21:53:36 +0100 Subject: [PATCH 16/16] Fix for better Mac support See https://github.com/MonoGame/MonoGame/pull/4286 --- MonoGame.Framework/MacOS/MacGamePlatform.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MonoGame.Framework/MacOS/MacGamePlatform.cs b/MonoGame.Framework/MacOS/MacGamePlatform.cs index 683cf7c48ff..7bc74667f8c 100644 --- a/MonoGame.Framework/MacOS/MacGamePlatform.cs +++ b/MonoGame.Framework/MacOS/MacGamePlatform.cs @@ -313,7 +313,9 @@ public override void EnterFullScreen() if (oldTitle != null) _gameWindow.Title = oldTitle; - _mainWindow.IsVisible = false; + //FIX: see https://github.com/MonoGame/MonoGame/pull/4286 + //_mainWindow.IsVisible = false; + // FIXME: EnterFullScreen gets called very early and interferes // with Synchronous mode, so disabling this for now. // Hopefully this does not cause excessive havoc. @@ -356,7 +358,9 @@ public override void ExitFullScreen() // Set the level here to normal _mainWindow.Level = NSWindowLevel.Normal; - _mainWindow.IsVisible = false; + //FIX: see https://github.com/MonoGame/MonoGame/pull/4286 + //_mainWindow.IsVisible = false; + // FIXME: EnterFullScreen gets called very early and interferes // with Synchronous mode, so disabling this for now. // Hopefully this does not cause excessive havoc.