private void uploadHorizontalPadding(ITextureUpload upload, RectangleI middleBounds, int actualPadding) { RectangleI[] sideBoundsArray = { new RectangleI(middleBounds.X - actualPadding, middleBounds.Y, actualPadding, middleBounds.Height).Intersect(atlasBounds), // Left new RectangleI(middleBounds.X + middleBounds.Width, middleBounds.Y, actualPadding, middleBounds.Height).Intersect(atlasBounds), // Right }; int[] sideIndices = { 0, // Left middleBounds.Width - 1, // Right }; for (int i = 0; i < 2; ++i) { RectangleI sideBounds = sideBoundsArray[i]; if (!sideBounds.IsEmpty) { bool allTransparentBlack = true; int index = sideIndices[i]; var sideUpload = new ArrayPoolTextureUpload(sideBounds.Width, sideBounds.Height) { Bounds = sideBounds }; int stride = middleBounds.Width; for (int y = 0; y < sideBounds.Height; ++y) { for (int x = 0; x < sideBounds.Width; ++x) { Rgba32 pixel = upload.Data[index + y * stride]; allTransparentBlack &= pixel == transparent_black; sideUpload.RawData[y * sideBounds.Width + x] = pixel; } } // Only upload padding if the border isn't completely transparent. if (!allTransparentBlack) { // For a texture atlas, we don't care about opacity, so we avoid // any computations related to it by assuming it to be mixed. base.SetData(sideUpload, WrapMode.None, WrapMode.None, Opacity.Mixed); } } } }
private void uploadCornerPadding(ITextureUpload upload, RectangleI middleBounds, int actualPadding) { RectangleI[] cornerBoundsArray = { new RectangleI(middleBounds.X - actualPadding, middleBounds.Y - actualPadding, actualPadding, actualPadding).Intersect(atlasBounds), // TopLeft new RectangleI(middleBounds.X + middleBounds.Width, middleBounds.Y - actualPadding, actualPadding, actualPadding).Intersect(atlasBounds), // TopRight new RectangleI(middleBounds.X - actualPadding, middleBounds.Y + middleBounds.Height, actualPadding, actualPadding).Intersect(atlasBounds), // BottomLeft new RectangleI(middleBounds.X + middleBounds.Width, middleBounds.Y + middleBounds.Height, actualPadding, actualPadding).Intersect(atlasBounds), // BottomRight }; int[] cornerIndices = { 0, // TopLeft middleBounds.Width - 1, // TopRight (middleBounds.Height - 1) * middleBounds.Width, // BottomLeft (middleBounds.Height - 1) * middleBounds.Width + middleBounds.Width - 1, // BottomRight }; for (int i = 0; i < 4; ++i) { RectangleI cornerBounds = cornerBoundsArray[i]; int nCornerPixels = cornerBounds.Width * cornerBounds.Height; Rgba32 cornerPixel = upload.Data[cornerIndices[i]]; // Only upload if we have a non-zero size and if the colour isn't already transparent black if (nCornerPixels > 0 && cornerPixel != transparent_black) { var cornerUpload = new ArrayPoolTextureUpload(cornerBounds.Width, cornerBounds.Height) { Bounds = cornerBounds }; for (int j = 0; j < nCornerPixels; ++j) { cornerUpload.RawData[j] = cornerPixel; } // For a texture atlas, we don't care about opacity, so we avoid // any computations related to it by assuming it to be mixed. base.SetData(cornerUpload, WrapMode.None, WrapMode.None, Opacity.Mixed); } } }
private void decodingLoop(CancellationToken cancellationToken) { var packet = ffmpeg.av_packet_alloc(); const int max_pending_frames = 3; try { while (true) { if (cancellationToken.IsCancellationRequested) { return; } if (decodedFrames.Count < max_pending_frames) { int readFrameResult = ffmpeg.av_read_frame(formatContext, packet); if (readFrameResult >= 0) { state = DecoderState.Running; if (packet->stream_index == stream->index) { if (ffmpeg.avcodec_send_packet(stream->codec, packet) < 0) { throw new Exception("Error sending packet."); } var result = ffmpeg.avcodec_receive_frame(stream->codec, frame); if (result == 0) { var frameTime = (frame->best_effort_timestamp - stream->start_time) * timeBaseInSeconds * 1000; if (!skipOutputUntilTime.HasValue || skipOutputUntilTime.Value < frameTime) { skipOutputUntilTime = null; SwsContext *swsCtx = null; try { swsCtx = ffmpeg.sws_getContext(codecParams.width, codecParams.height, (AVPixelFormat)frame->format, codecParams.width, codecParams.height, AVPixelFormat.AV_PIX_FMT_RGBA, 0, null, null, null); ffmpeg.sws_scale(swsCtx, frame->data, frame->linesize, 0, frame->height, ffmpegFrame->data, ffmpegFrame->linesize); } finally { ffmpeg.sws_freeContext(swsCtx); } if (!availableTextures.TryDequeue(out var tex)) { tex = new Texture(codecParams.width, codecParams.height, true); } var upload = new ArrayPoolTextureUpload(tex.Width, tex.Height); // todo: can likely make this more efficient new Span <Rgba32>(ffmpegFrame->data[0], uncompressedFrameSize / 4).CopyTo(upload.RawData); tex.SetData(upload); decodedFrames.Enqueue(new DecodedFrame { Time = frameTime, Texture = tex }); } lastDecodedFrameTime = (float)frameTime; } } } else if (readFrameResult == ffmpeg.AVERROR_EOF) { if (Looping) { Seek(0); } else { state = DecoderState.EndOfStream; } } else { state = DecoderState.Ready; Thread.Sleep(1); } } else { // wait until existing buffers are consumed. state = DecoderState.Ready; Thread.Sleep(1); } while (!decoderCommands.IsEmpty) { if (cancellationToken.IsCancellationRequested) { return; } if (decoderCommands.TryDequeue(out var cmd)) { cmd(); } } } } catch (Exception) { state = DecoderState.Faulted; } finally { ffmpeg.av_packet_free(&packet); if (state != DecoderState.Faulted) { state = DecoderState.Stopped; } } }