Inheritance: IDisposable
    public void Render()
    {
      if (_player == null)
      {
        return;
      }

      lock (_subtitleLock)
      {
        if (_clearOnNextRender)
        {
          //Log.Debug("SubtitleRenderer: clearOnNextRender");
          _clearOnNextRender = false;
          if (_subTexture != null)
          {
            _subTexture.SafeDispose();
          }
          _subTexture = null;
          _currentSubtitle = null;
        }

        if (_renderSubtitles == false)
        {
          return;
        }

        // ugly temp!
        bool timeForNext = false;
        if (_subtitles.Count > 0)
        {
          Subtitle next = _subtitles.First.Value;
          if (next.presentTime <= _player.StreamPosition)
          {
            timeForNext = true;
          }
        }

        _posOnLastRender = _player.StreamPosition;

        // Check for subtitle if we dont have one currently or if the current one is beyond its timeout
        if (_currentSubtitle == null ||
            _currentSubtitle.presentTime + _currentSubtitle.timeOut <= _player.StreamPosition ||
            timeForNext)
        {
          //Log.Debug("-Current position: ");
          if (_currentSubtitle != null && !timeForNext)
          {
            //Log.Debug("-Current subtitle : " + currentSubtitle.ToString() + " time out expired");
            _currentSubtitle = null;
          }
          if (timeForNext)
          {
            //if (currentSubtitle != null) Log.Debug("-Current subtitle : " + currentSubtitle.ToString() + " TIME FOR NEXT!");
          }

          Subtitle next = null;
          while (_subtitles.Count > 0)
          {
            next = _subtitles.First.Value;

            //Log.Debug("-next from queue: " + next.ToString());
            // if the next should be displayed now or previously
            if (next.presentTime <= _player.StreamPosition)
            {
              // remove from queue
              _subtitles.RemoveFirst();

              // if it is not too late for this sub to be displayed, break
              // otherwise continue
              if (next.presentTime + next.timeOut >= _player.StreamPosition)
              {
                _currentSubtitle = next;
                break;
              }
            }
              // next wants to be displayed in the future so break
            else
            {
              //Log.Debug("-next is in the future");
              break;
            }
          }
          // if currentSubtitle is non-null we have a new subtitle
          if (_currentSubtitle != null)
          {
            SetSubtitle(_currentSubtitle);
          }
          else
          {
            return;
          }
        }
        
        VertexFormats vertexFormat = GUIGraphicsContext.DX9Device.VertexFormat;

        try
        {
          int wx = 0, wy = 0, wwidth = 0, wheight = 0;
          float rationW = 1, rationH = 1;

          Rectangle src, dst;
          if (VMR9Util.g_vmr9 != null)
          {
            VMR9Util.g_vmr9.GetVideoWindows(out src, out dst);

            rationH = dst.Height/(float) _currentSubtitle.screenHeight;

            // Get the location to render the subtitle to for blu-ray
            if (_currentSubtitle.horizontalPosition != 0)
            {
              rationW = dst.Width/(float) _currentSubtitle.screenWidth;
              wx = dst.X + (int) (rationW*(float) _currentSubtitle.horizontalPosition);
            }
            else
            {
              rationW = rationH;
              wx = dst.X + (int) ((dst.Width - _currentSubtitle.width*rationW)/2);
            }
            wy = dst.Y + (int) (rationH*_currentSubtitle.firstScanLine);

            wwidth = (int) (_currentSubtitle.width*rationW);
            wheight = (int) (_currentSubtitle.height*rationH);

            // make sure the vertex buffer is ready and correct for the coordinates
            CreateVertexBuffer(wx, wy, wwidth, wheight);
          }

          // Log.Debug("Subtitle render target: wx = {0} wy = {1} ww = {2} wh = {3}", wx, wy, wwidth, wheight);

          // enable alpha blending so that the subtitle is rendered with transparent background
          DXNative.FontEngineSetRenderState((int)D3DRENDERSTATETYPE.D3DRS_ALPHABLENDENABLE, 1);

          // Make sure D3D objects haven't been disposed for some reason. This would  cause
          // an access violation on native side, causing Skin Engine to halt rendering
          if (_subTexture != null && (!_subTexture.Disposed && !_vertexBuffer.Disposed))
          {
            GUIGraphicsContext.DX9Device.SetStreamSource(0, _vertexBuffer, 0);
            GUIGraphicsContext.DX9Device.SetTexture(0, _subTexture);
            GUIGraphicsContext.DX9Device.VertexFormat = CustomVertex.TransformedTextured.Format;
            GUIGraphicsContext.DX9Device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
          }
          else
          {
            Log.Debug("Subtitle renderer: D3D resource was disposed! Not trying to render the texture");
          }
        }
        catch (Exception e)
        {
          Log.Error(e);
        }

        try
        {
          // Restore device settings
          GUIGraphicsContext.DX9Device.SetTexture(0, null);
          GUIGraphicsContext.DX9Device.VertexFormat = vertexFormat;
        }
        catch (Exception e)
        {
          Log.Error(e);
        }
      } // end of lock (subtitle)
    }
    public void OnTextSubtitle(ref TEXT_SUBTITLE sub)
    {
      try
      {
        if (sub.page == _activeSubPage)
        {
          Log.Debug("Page: " + sub.page);
          Log.Debug("Character table: " + sub.encoding);
          Log.Debug("Timeout: " + sub.timeOut);
          Log.Debug("Timestamp: " + sub.timeStamp);
          Log.Debug("Language: " + sub.language);

          String content = sub.text;
          if (content == null)
          {
            Log.Error("OnTextSubtitle: sub.txt == null!");
            return;
          }
          Log.Debug("Content: ");
          if (content.Trim().Length > 0) // debug log subtitles
          {
            StringTokenizer st = new StringTokenizer(content, new char[] {'\n'});
            while (st.HasMore)
            {
              Log.Debug(st.NextToken());
            }
          }
          else
          {
            Log.Debug("Page: <BLANK PAGE>");
          }
        }
      }
      catch (Exception e)
      {
        Log.Error("Problem with TEXT_SUBTITLE");
        Log.Error(e);
      }

      try
      {
        // if we dont need the subtitle
        if (!_renderSubtitles || _useBitmap || (_activeSubPage != sub.page))
        {
          //
          //chemelli: too much logging. You can check if logs have:
          //          Log.Debug("Page: " + sub.page);  or Log.Debug("Page: <BLANK PAGE>");
          //          and
          //          Log.Debug("Text subtitle (page {0}) ACCEPTED: [...]
          //          to know the evaluation of this if block
          //
          //Log.Debug("Text subtitle (page {0}) discarded: useBitmap is {1} and activeSubPage is {2}", sub.page, useBitmap,
          //          activeSubPage);

          return;
        }
        Log.Debug("Text subtitle (page {0}) ACCEPTED: useBitmap is {1} and activeSubPage is {2}", sub.page, _useBitmap,
                  _activeSubPage);

        Subtitle subtitle = new Subtitle();

        // TODO - RenderText should directly draw to a D3D texture
        subtitle.subBitmap = RenderText(sub.lc);
        subtitle.timeOut = sub.timeOut;
        subtitle.presentTime = sub.timeStamp / 90000.0f + _startPos;

        subtitle.height = 576;
        subtitle.width = 720;
        subtitle.screenHeight = 576;
        subtitle.screenWidth = 720;
        subtitle.firstScanLine = 0;
        subtitle.horizontalPosition = 0;

        Texture texture = null;
        try
        {
          // allocate new texture
          texture = new Texture(GUIGraphicsContext.DX9Device, subtitle.subBitmap.Width,
                                subtitle.subBitmap.Height, 1, Usage.Dynamic, Format.A8R8G8B8, Pool.Default);
          int pitch;
          using (GraphicsStream a = texture.LockRectangle(0, LockFlags.Discard, out pitch))
          {
            BitmapData bd = subtitle.subBitmap.LockBits(new Rectangle(0, 0, subtitle.subBitmap.Width,
                                                                      subtitle.subBitmap.Height), ImageLockMode.ReadOnly,
                                                        PixelFormat.Format32bppArgb);

            // Quick copy of content
            unsafe
            {
              byte* to = (byte*)a.InternalDataPointer;
              byte* from = (byte*)bd.Scan0.ToPointer();
              for (int y = 0; y < bd.Height; ++y)
              {
                for (int x = 0; x < bd.Width * 4; ++x)
                {
                  to[pitch * y + x] = from[y * bd.Stride + x];
                }
              }
            }

            texture.UnlockRectangle(0);
            subtitle.subBitmap.UnlockBits(bd);
            subtitle.subBitmap.SafeDispose();
            subtitle.subBitmap = null;
            subtitle.texture = texture;
            a.Close();
          }
        }
        catch (Exception e)
        {
          Log.Debug("SubtitleRenderer: Failed to create subtitle surface!");
          Log.Error(e);
          return;
        }

        AddSubtitle(subtitle);
      }
      catch (Exception e)
      {
        Log.Error("Problem processing text subtitle");
        Log.Error(e);
      }
    }
    private void SetSubtitle(Subtitle subtitle)
    {
      try
      {
        lock (_subtitleLock)
        {
          Log.Debug("SubtitleRenderer: SetSubtitle : " + subtitle.ToString());

          // dispose of old subtitle
          _subTexture.SafeDispose();
          _subTexture = null;

          // set new subtitle
          if (subtitle != null)
          {
            _subTexture = subtitle.texture;
            _currentSubtitle = subtitle;

            _currentSubtitle.subBitmap.SafeDispose();
            _currentSubtitle.subBitmap = null;
          }
        }
      }
      catch (Exception e)
      {
        Log.Error("SubtitleRenderer: SetSubtitle exception: {0} {1}", e.Message, e.StackTrace);
      }
    }
    /// <summary>
    /// Callback from subtitle filter, alerting us that a new subtitle is available
    /// It receives the new subtitle as the argument sub, which data is only valid 
    /// for the duration of OnSubtitle.
    /// </summary>
    /// <returns></returns>
    public int OnSubtitle(ref NATIVE_SUBTITLE sub)
    {
      if (!_useBitmap || !_renderSubtitles)
      {
        return 0;
        // TODO: Might be good to let this cache and then check in Render method because bitmap subs arrive a while before display
      }
      Log.Debug("OnSubtitle - stream position " + _player.StreamPosition);
      lock (_alert)
      {
        try
        {
          Log.Debug("SubtitleRenderer:  Bitmap: bpp=" + sub.bmBitsPixel + " planes " + sub.bmPlanes + " dim = " +
                    sub.bmWidth + " x " + sub.bmHeight + " stride : " + sub.bmWidthBytes);
          Log.Debug("SubtitleRenderer: to = " + sub.timeOut + " ts=" + sub.timeStamp + " fsl=" + sub.firstScanLine + 
            " h pos=" + sub.horizontalPosition + " (startPos = " + _startPos + ")");

          Subtitle subtitle = new Subtitle();
          subtitle.subBitmap = new Bitmap(sub.bmWidth, sub.bmHeight, PixelFormat.Format32bppArgb);
          subtitle.timeOut = sub.timeOut;
          subtitle.presentTime = ((double)sub.timeStamp / 1000.0f) + _startPos; // compute present time in SECONDS
          subtitle.height = (uint)sub.bmHeight;
          subtitle.width = (uint)sub.bmWidth;
          subtitle.screenHeight = (uint)sub.screenHeight;
          subtitle.screenWidth = (uint)sub.screenWidth;
          subtitle.firstScanLine = sub.firstScanLine;
          subtitle.horizontalPosition = sub.horizontalPosition;
          subtitle.id = _subCounter++;
          //Log.Debug("Received Subtitle : " + subtitle.ToString());

          Texture texture = null;
          try
          {
            // allocate new texture
            texture = new Texture(GUIGraphicsContext.DX9Device, (int)subtitle.width, (int)subtitle.height, 1,
                                  Usage.Dynamic,
                                  Format.A8R8G8B8, GUIGraphicsContext.GetTexturePoolType());

            if (texture == null)
            {
              Log.Debug("OnSubtitle: Failed to create new texture!");
              return 0;
            }

            int pitch;
            using (GraphicsStream a = texture.LockRectangle(0, LockFlags.Discard, out pitch))
            {
              // Quick copy of content
              unsafe
              {
                byte* to = (byte*)a.InternalDataPointer;
                byte* from = (byte*)sub.bmBits;
                for (int y = 0; y < sub.bmHeight; ++y)
                {
                  for (int x = 0; x < sub.bmWidth * 4; ++x)
                  {
                    to[pitch * y + x] = from[y * sub.bmWidthBytes + x];
                  }
                }
              }
              a.Close();
            }

            texture.UnlockRectangle(0);
            subtitle.texture = texture;
          }
          catch (Exception)
          {
            Log.Debug("OnSubtitle: Failed to copy bitmap data!");
            return 0;
          }

          AddSubtitle(subtitle);
        }
        catch (Exception e)
        {
          Log.Error(e);
        }
      }
      return 0;
    }
 private void AddSubtitle(Subtitle sub)
 {
   lock (_subtitleLock)
   {
     while (_subtitles.Count >= MAX_SUBTITLES_IN_QUEUE)
     {
       Log.Debug("SubtitleRenderer: Subtitle queue too big, discarding first element");
       _subtitles.First.Value.SafeDispose();
       _subtitles.RemoveFirst();
     }
     _subtitles.AddLast(sub);
     Log.Debug("SubtitleRenderer: Subtitle added, now have " + _subtitles.Count + " subtitles in cache");
   }
 }
    public void Render()
    {
      if (_player == null)
      {
        return;
      }

      lock (_subtitleLock)
      {
        if (_clearOnNextRender)
        {
          //Log.Debug("SubtitleRenderer: clearOnNextRender");
          _clearOnNextRender = false;
          if (_subTexture != null)
          {
            _subTexture.SafeDispose();
          }
          _subTexture = null;
          _currentSubtitle = null;
        }

        if (_renderSubtitles == false)
        {
          return;
        }

        // ugly temp!
        bool timeForNext = false;
        if (_subtitles.Count > 0)
        {
          Subtitle next = _subtitles.First.Value;
          if (next.presentTime <= _player.StreamPosition)
          {
            timeForNext = true;
          }
        }

        _posOnLastRender = _player.StreamPosition;

        // Check for subtitle if we dont have one currently or if the current one is beyond its timeout
        if (_currentSubtitle == null ||
            _currentSubtitle.presentTime + _currentSubtitle.timeOut <= _player.StreamPosition ||
            timeForNext)
        {
          //Log.Debug("-Current position: ");
          if (_currentSubtitle != null && !timeForNext)
          {
            //Log.Debug("-Current subtitle : " + currentSubtitle.ToString() + " time out expired");
            _currentSubtitle = null;
          }
          if (timeForNext)
          {
            //if (currentSubtitle != null) Log.Debug("-Current subtitle : " + currentSubtitle.ToString() + " TIME FOR NEXT!");
          }

          Subtitle next = null;
          while (_subtitles.Count > 0)
          {
            next = _subtitles.First.Value;

            //Log.Debug("-next from queue: " + next.ToString());
            // if the next should be displayed now or previously
            if (next.presentTime <= _player.StreamPosition)
            {
              // remove from queue
              _subtitles.RemoveFirst();

              // if it is not too late for this sub to be displayed, break
              // otherwise continue
              if (next.presentTime + next.timeOut >= _player.StreamPosition)
              {
                _currentSubtitle = next;
                break;
              }
            }
              // next wants to be displayed in the future so break
            else
            {
              //Log.Debug("-next is in the future");
              break;
            }
          }
          // if currentSubtitle is non-null we have a new subtitle
          if (_currentSubtitle != null)
          {
            SetSubtitle(_currentSubtitle);
          }
          else
          {
            return;
          }
        }
        //bool alphaTest = false;
        bool alphaBlend = false;
        VertexFormats vertexFormat = CustomVertex.TransformedColoredTextured.Format;

        try
        {
          // store current settings so they can be restored when we are done
          alphaBlend = GUIGraphicsContext.DX9Device.GetRenderStateBoolean(RenderStates.AlphaBlendEnable);
          vertexFormat = GUIGraphicsContext.DX9Device.VertexFormat;

          int wx = 0, wy = 0, wwidth = 0, wheight = 0;
          float rationW = 1, rationH = 1;

          if (GUIGraphicsContext.IsFullScreenVideo)
          {
            rationH = GUIGraphicsContext.Height / (float)_currentSubtitle.screenHeight;
            rationW = rationH;

            // Get the location to render the subtitle to
            wx = GUIGraphicsContext.OverScanLeft +
                 (int)(((float)(GUIGraphicsContext.Width - _currentSubtitle.width * rationW)) / 2);
            wy = GUIGraphicsContext.OverScanTop + (int)(rationH * (float)_currentSubtitle.firstScanLine);
          }
          else // Video overlay
          {
            rationH = GUIGraphicsContext.VideoWindow.Height / (float)_currentSubtitle.screenHeight;
            rationW = rationH;

            wx = GUIGraphicsContext.VideoWindow.Right - (GUIGraphicsContext.VideoWindow.Width / 2) -
                 (int)(((float)_currentSubtitle.width * rationW) / 2);
            wy = GUIGraphicsContext.VideoWindow.Top + (int)(rationH * (float)_currentSubtitle.firstScanLine);
          }

          wwidth = (int)((float)_currentSubtitle.width * rationW);
          wheight = (int)((float)_currentSubtitle.height * rationH);

          // make sure the vertex buffer is ready and correct for the coordinates
          CreateVertexBuffer(wx, wy, wwidth, wheight);

          // Log.Debug("Subtitle render target: wx = {0} wy = {1} ww = {2} wh = {3}", wx, wy, wwidth, wheight);

          // enable alpha blending so that the subtitle is rendered with transparent background
          FontEngineSetAlphaBlend(1); //TRUE

          // Make sure D3D objects haven't been disposed for some reason. This would  cause
          // an access violation on native side, causing Skin Engine to halt rendering
          if (!_subTexture.Disposed && !_vertexBuffer.Disposed)
          {
            GUIGraphicsContext.DX9Device.SetStreamSource(0, _vertexBuffer, 0);
            GUIGraphicsContext.DX9Device.SetTexture(0, _subTexture);
            GUIGraphicsContext.DX9Device.VertexFormat = CustomVertex.TransformedTextured.Format;
            GUIGraphicsContext.DX9Device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
          }
          else
          {
            Log.Debug("Subtitle renderer: D3D resource was disposed! Not trying to render the texture");
          }
        }
        catch (Exception e)
        {
          Log.Error(e);
        }

        try
        {
          // Restore device settings
          GUIGraphicsContext.DX9Device.SetTexture(0, null);
          GUIGraphicsContext.DX9Device.VertexFormat = vertexFormat;
        }
        catch (Exception e)
        {
          Log.Error(e);
        }
      } // end of lock (subtitle)
    }
    public void OnTextSubtitle(ref TEXT_SUBTITLE sub)
    {
      try
      {
        if (sub.page == _activeSubPage)
        {
          Log.Debug("Page: " + sub.page);
          Log.Debug("Character table: " + sub.encoding);
          Log.Debug("Timeout: " + sub.timeOut);
          Log.Debug("Timestamp: " + sub.timeStamp);
          Log.Debug("Language: " + sub.language);

          String content = sub.text;
          if (content == null)
          {
            Log.Error("OnTextSubtitle: sub.txt == null!");
            return;
          }
          Log.Debug("Content: ");
          if (content.Trim().Length > 0) // debug log subtitles
          {
            StringTokenizer st = new StringTokenizer(content, new char[] {'\n'});
            while (st.HasMore)
            {
              Log.Debug(st.NextToken());
            }
          }
          else
          {
            Log.Debug("Page: <BLANK PAGE>");
          }
        }
      }
      catch (Exception e)
      {
        Log.Error("Problem with TEXT_SUBTITLE");
        Log.Error(e);
      }

      try
      {
        // if we dont need the subtitle
        if (!_renderSubtitles || _useBitmap || (_activeSubPage != sub.page))
        {
          //
          //chemelli: too much logging. You can check if logs have:
          //          Log.Debug("Page: " + sub.page);  or Log.Debug("Page: <BLANK PAGE>");
          //          and
          //          Log.Debug("Text subtitle (page {0}) ACCEPTED: [...]
          //          to know the evaluation of this if block
          //
          //Log.Debug("Text subtitle (page {0}) discarded: useBitmap is {1} and activeSubPage is {2}", sub.page, useBitmap,
          //          activeSubPage);

          return;
        }
        Log.Debug("Text subtitle (page {0}) ACCEPTED: useBitmap is {1} and activeSubPage is {2}", sub.page, _useBitmap,
                  _activeSubPage);

        Subtitle subtitle = new Subtitle();

        // TODO - RenderText should directly draw to a D3D texture
        subtitle.subBitmap = RenderText(sub.lc);
        subtitle.timeOut = sub.timeOut;
        subtitle.presentTime = sub.timeStamp / 90000.0f + _startPos;

        subtitle.height = 576;
        subtitle.width = 720;
        subtitle.screenHeight = 576;
        subtitle.screenWidth = 720;
        subtitle.firstScanLine = 0;

        Texture texture = null;
        try
        {
          // allocate new texture
          texture = new Texture(GUIGraphicsContext.DX9Device, subtitle.subBitmap.Width,
                                subtitle.subBitmap.Height, 1, Usage.Dynamic, Format.A8R8G8B8, Pool.Default);
          int pitch;
          using (GraphicsStream a = texture.LockRectangle(0, LockFlags.Discard, out pitch))
          {
            BitmapData bd = subtitle.subBitmap.LockBits(new Rectangle(0, 0, subtitle.subBitmap.Width,
                                                                      subtitle.subBitmap.Height), ImageLockMode.ReadOnly,
                                                        PixelFormat.Format32bppArgb);

            // Quick copy of content
            unsafe
            {
              byte* to = (byte*)a.InternalDataPointer;
              byte* from = (byte*)bd.Scan0.ToPointer();
              for (int y = 0; y < bd.Height; ++y)
              {
                for (int x = 0; x < bd.Width * 4; ++x)
                {
                  to[pitch * y + x] = from[y * bd.Stride + x];
                }
              }
            }

            texture.UnlockRectangle(0);
            subtitle.subBitmap.UnlockBits(bd);
            subtitle.subBitmap.SafeDispose();
            subtitle.subBitmap = null;
            subtitle.texture = texture;
            a.Close();
          }
        }
        catch (Exception e)
        {
          Log.Debug("SubtitleRenderer: Failed to create subtitle surface!");
          Log.Error(e);
          return;
        }

        AddSubtitle(subtitle);
      }
      catch (Exception e)
      {
        Log.Error("Problem processing text subtitle");
        Log.Error(e);
      }
    }
    /// <summary>
    /// Callback from subtitle filter, alerting us that a new subtitle is available
    /// It receives the new subtitle as the argument sub, which data is only valid 
    /// for the duration of OnSubtitle.
    /// </summary>
    /// <returns></returns>
    public int OnSubtitle(ref NATIVE_SUBTITLE sub)
    {
      if (!_useBitmap || !_renderSubtitles)
      {
        return 0;
        // TODO: Might be good to let this cache and then check in Render method because bitmap subs arrive a while before display
      }
      Log.Debug("OnSubtitle - stream position " + _player.StreamPosition);
      lock (_alert)
      {
        try
        {
          Log.Debug("SubtitleRenderer:  Bitmap: bpp=" + sub.bmBitsPixel + " planes " + sub.bmPlanes + " dim = " +
                    sub.bmWidth + " x " + sub.bmHeight + " stride : " + sub.bmWidthBytes);
          Log.Debug("SubtitleRenderer: to = " + sub.timeOut + " ts=" + sub.timeStamp + " fsl=" + sub.firstScanLine +
                    " (startPos = " + _startPos + ")");

          Subtitle subtitle = new Subtitle();
          subtitle.subBitmap = new Bitmap(sub.bmWidth, sub.bmHeight, PixelFormat.Format32bppArgb);
          subtitle.timeOut = sub.timeOut;
          subtitle.presentTime = ((double)sub.timeStamp / 1000.0f) + _startPos; // compute present time in SECONDS
          subtitle.height = (uint)sub.bmHeight;
          subtitle.width = (uint)sub.bmWidth;
          subtitle.screenHeight = (uint)sub.screenHeight;
          subtitle.screenWidth = (uint)sub.screenWidth;
          subtitle.firstScanLine = sub.firstScanLine;
          subtitle.id = _subCounter++;
          //Log.Debug("Received Subtitle : " + subtitle.ToString());

          Texture texture = null;
          try
          {
            // allocate new texture
            texture = new Texture(GUIGraphicsContext.DX9Device, (int)subtitle.width, (int)subtitle.height, 1,
                                  Usage.Dynamic,
                                  Format.A8R8G8B8, Pool.Default);

            if (texture == null)
            {
              Log.Debug("OnSubtitle: Failed to create new texture!");
              return 0;
            }

            int pitch;
            using (GraphicsStream a = texture.LockRectangle(0, LockFlags.Discard, out pitch))
            {
              // Quick copy of content
              unsafe
              {
                byte* to = (byte*)a.InternalDataPointer;
                byte* from = (byte*)sub.bmBits;
                for (int y = 0; y < sub.bmHeight; ++y)
                {
                  for (int x = 0; x < sub.bmWidth * 4; ++x)
                  {
                    to[pitch * y + x] = from[y * sub.bmWidthBytes + x];
                  }
                }
              }
              a.Close();
            }

            texture.UnlockRectangle(0);
            subtitle.texture = texture;
          }
          catch (Exception)
          {
            Log.Debug("OnSubtitle: Failed to copy bitmap data!");
            return 0;
          }

          AddSubtitle(subtitle);
        }
        catch (Exception e)
        {
          Log.Error(e);
        }
      }
      return 0;
    }