paint.net插件开发之二:在插件中集成OpenCL

OpenCL(Open Computing Language)是一种异构计算框架,可以在不同厂商的不同设备上执行并行计算任务,内核代码与c语言高度相似,网上的相关资料有很多,不做赘述,直接进入正题。

  1. 在BoltBait的codelab插件当中,新建一个插件,保存之后点构建dll。
    在这里插入图片描述2. 可以看到下方有个“生成VS解决方案”,点它。
    在这里插入图片描述

  2. 好,现在我们已经生成完整的.net解决方案,我用的paint.net是5.0的,依赖于.net 7.0,但是写这篇文章的时候paint.net版本迭代到5.1了,5.1版本依赖的…net 7.0架构在Codelab最新版本6.12中并不支持。也许BoltBait后续会更新,再次之前还是先使用5.0.x版本。
    现在打开nuget包管理器,在里面搜索OpenCL.Net
    在这里插入图片描述
    右键解决方案窗口中的项目
    在这里插入图片描述
    在这里插入图片描述
    安装

  3. 在代码中添加

// 其他依赖的包...
using OpenCL.Net;
using OpenCL.Net.Extensions;
// ...
namespace MyScriptEffect {
// ...
    public static class Opencl
    {
        private const string CLCode = @"
__kernel void mark_red(
    const int width,
    const int height,
    const int channel, // RGBA
    __global int* image
) {
    int x = get_global_id(0);
    int y = get_global_id(1);
    for (int c=0;c<channel;c++) {
        int idx = x + y * width + c * width * height;
        if (c == 0 || c == 3) {
            image[idx] = 0xFF;
        } else {
            image[idx] = 0x00;
        }
    }
}
";
        private static OpenCL.Net.Platform m_platform;
        private static OpenCL.Net.Device[] m_devices;
        private static OpenCL.Net.Context m_context;
        private static OpenCL.Net.CommandQueue m_commandQueue;
        private static OpenCL.Net.Program m_program;
        private static bool m_hasInit = false;
        static Opencl()
        {
            AppDomain.CurrentDomain.ProcessExit += (s, e) => Cleanup();
            Init();
        }
        public static void Init()
        {
            if (m_hasInit) return;
            ErrorCode err;
            uint platformCount;
            err = Cl.GetPlatformIDs(0, null, out platformCount);
            if (err != ErrorCode.Success)
            {
                throw new PaintDotNet.InternalErrorException(string.Format("Create platform error: {0}", err.ToString()));
            }
            Platform[] platforms = new Platform[platformCount];
            err = Cl.GetPlatformIDs(platformCount, platforms, out platformCount);
            if (err != ErrorCode.Success)
            {
                throw new PaintDotNet.InternalErrorException(string.Format("Create platform error: {0}", err.ToString()));
            }
            m_platform = platforms[0];
            uint deviceCount;
            err = Cl.GetDeviceIDs(m_platform, DeviceType.Gpu, 0, null, out deviceCount);
            if (err == ErrorCode.DeviceNotFound)
            {
                err = Cl.GetDeviceIDs(m_platform, DeviceType.Cpu, 0, null, out deviceCount);
                if (err != ErrorCode.Success)
                {
                    throw new PaintDotNet.InternalErrorException(string.Format("Create cpu device error: {0}",err.ToString()));
                }
                m_devices = new Device[deviceCount];
                err = Cl.GetDeviceIDs(m_platform, DeviceType.Cpu, deviceCount, m_devices, out deviceCount);
                if (err != ErrorCode.Success)
                {
                    throw new PaintDotNet.InternalErrorException(string.Format("Create cpu device error: {0}", err.ToString()));
                }
            } else if (err != ErrorCode.Success) {
                throw new PaintDotNet.InternalErrorException(string.Format("Create gpu device error: {0}", err.ToString()));
            } else
            {
                m_devices = new Device[deviceCount];
                err = Cl.GetDeviceIDs(m_platform, DeviceType.Gpu, deviceCount, m_devices, out deviceCount);
                if (err != ErrorCode.Success)
                {
                    throw new PaintDotNet.InternalErrorException(string.Format("Create gpu device error: {0}", err.ToString()));
                }
            }
            m_context = Cl.CreateContext(null, 1, new[] { m_devices[0] }, null, IntPtr.Zero, out err);
            if (err != ErrorCode.Success)
            {
                throw new PaintDotNet.InternalErrorException(string.Format("Create context error: {0}", err.ToString()));
            }
            m_commandQueue = Cl.CreateCommandQueue(m_context, m_devices[0], 0, out err);
            if (err != ErrorCode.Success)
            {
                throw new PaintDotNet.InternalErrorException(string.Format("Create command queue error: {0}", err.ToString()));
            }
            m_program = Cl.CreateProgramWithSource(m_context, 1, new[] { CLCode }, null, out err);
            Cl.BuildProgram(m_program, 1, new[] { m_devices[0] }, null, null, IntPtr.Zero);
            if (err != ErrorCode.Success)
            {
                InfoBuffer log = Cl.GetProgramBuildInfo(m_program, m_devices[0], ProgramBuildInfo.Log, out err);
                if (err != ErrorCode.Success)
                {
                    throw new PaintDotNet.InternalErrorException(string.Format("Build program error, fail to get build log: {0}", err.ToString()));
                }
                throw new PaintDotNet.InternalErrorException(string.Format("Build program error, log:\n{1}", log.ToString()));
            }
            m_hasInit = true;
        } // Opencl::Init
        public static void Cleanup()
        {
            if (!m_hasInit) return;
            m_program.Dispose();
            m_commandQueue.Dispose();
            m_context.Dispose();
        } // Opencl::Cleanup
        public static unsafe IntPtr ToIntPtr(this int[] obj)
        {
            IntPtr PtrA = IntPtr.Zero;
            fixed (int* Ap = obj) return new IntPtr(Ap);
        }
        public static void Execute(int width, int height, int channel, int[] buffer)
        {
            ErrorCode err;
            OpenCL.Net.Kernel kernel = Cl.CreateKernel(m_program, "mark_red", out err);
            if (err != ErrorCode.Success)
            {
                throw new PaintDotNet.InternalErrorException(string.Format("Create kernel error: {0}", err.ToString()));
            }
            err = Cl.SetKernelArg(kernel, 0, width);
            err = Cl.SetKernelArg(kernel, 1, height);
            err = Cl.SetKernelArg(kernel, 2, channel);
            IMem clBuffer = Cl.CreateBuffer(m_context, MemFlags.ReadWrite | MemFlags.CopyHostPtr, sizeof(int) * buffer.Length, buffer.ToIntPtr(), out err);
            err = Cl.SetKernelArg(kernel, 3, clBuffer);
            nint[] globalWorkSize = new nint[] { (nint)width, (nint)height };
            err = Cl.EnqueueNDRangeKernel(m_commandQueue, kernel, 2, null, globalWorkSize, null, 0, null, out _);
            //Cl.EnqueueBarrier(m_commandQueue);
            err = Cl.EnqueueReadBuffer(m_commandQueue, clBuffer, Bool.True, 0, sizeof(int) * buffer.Length, buffer.ToIntPtr(), 0, null, out _);
            m_commandQueue.Finish();
            clBuffer.Dispose();
            kernel.Dispose();
        }
    } // class Opencl
    // ...
}

这个内核的功能很简单,就是把区域全部涂红,其实完全不需要用opencl实现,这是为了教程展示才这么做的。
4. 下面的MyScriptEffectPlugin.OnRender方法当中把ROI区域传入内核执行

// ...
protected override void OnRender(IBitmapEffectOutput output)
{
    using IEffectInputBitmap<ColorBgra32> sourceBitmap = Environment.GetSourceBitmapBgra32();
    using IBitmapLock<ColorBgra32> sourceLock = sourceBitmap.Lock(new RectInt32(0, 0, sourceBitmap.Size));
    RegionPtr<ColorBgra32> sourceRegion = sourceLock.AsRegionPtr();

    RectInt32 outputBounds = output.Bounds;
    using IBitmapLock<ColorBgra32> outputLock = output.LockBgra32();
    RegionPtr<ColorBgra32> outputSubRegion = outputLock.AsRegionPtr();
    var outputRegion = outputSubRegion.OffsetView(-outputBounds.Location);
    //uint seed = RandomNumber.InitializeSeed(RandomNumberRenderSeed, outputBounds.Location);

    // Delete any of these lines you don't need
    ColorBgra32 primaryColor = Environment.PrimaryColor;
    ColorBgra32 secondaryColor = Environment.SecondaryColor;
    int canvasCenterX = Environment.Document.Size.Width / 2;
    int canvasCenterY = Environment.Document.Size.Height / 2;
    var selection = Environment.Selection.RenderBounds;
    int selectionCenterX = (selection.Right - selection.Left) / 2 + selection.Left;
    int selectionCenterY = (selection.Bottom - selection.Top) / 2 + selection.Top;

    // Loop through the output canvas tile
    int[] buffer = new int[outputBounds.Width * outputBounds.Height * 4];
    for (int y = 0; y < outputBounds.Height; ++y)
    {
        if (IsCancelRequested) return;
        for (int x = 0; x < outputBounds.Width; ++x)
        {
            int _x = outputBounds.Left + x;
            int _y = outputBounds.Top + y;
            buffer[x + y * outputBounds.Width + 0 * outputBounds.Width * outputBounds.Height] = sourceRegion[_x, _y].R; // R
            buffer[x + y * outputBounds.Width + 1 * outputBounds.Width * outputBounds.Height] = sourceRegion[_x, _y].G; // G
            buffer[x + y * outputBounds.Width + 2 * outputBounds.Width * outputBounds.Height] = sourceRegion[_x, _y].B; // B
            buffer[x + y * outputBounds.Width + 3 * outputBounds.Width * outputBounds.Height] = sourceRegion[_x, _y].A; // A
        }
    }
    Opencl.Execute(outputBounds.Width, outputBounds.Height, 4, buffer);
    // save pixel
    for (int y = 0; y < outputBounds.Height; ++y)
    {
        if (IsCancelRequested) return;

        for (int x = 0; x < outputBounds.Width; ++x)
        {
            int _x = outputBounds.Left + x;
            int _y = outputBounds.Top + y;
            outputRegion[_x, _y].R = (byte)(buffer[x + y * outputBounds.Width + 0 * outputBounds.Width * outputBounds.Height]); // R
            outputRegion[_x, _y].G = (byte)(buffer[x + y * outputBounds.Width + 1 * outputBounds.Width * outputBounds.Height]); // G
            outputRegion[_x, _y].B = (byte)(buffer[x + y * outputBounds.Width + 2 * outputBounds.Width * outputBounds.Height]); // B
            outputRegion[_x, _y].A = (byte)(buffer[x + y * outputBounds.Width + 3 * outputBounds.Width * outputBounds.Height]); // A
        }
    }
    // save pixel end
} // OnRender
// ...
  1. 生成
    在这里插入图片描述
    在这里插入图片描述
  2. 找到paint.net安装目录下的Effects文件夹,下面有个CodeLab.dll,这是之前安装的开发插件,把刚刚编译生成的dll和nuget包目录下的OpenCL.Net.dll一起拷过来。
    在这里插入图片描述
  3. 启动paint.net看看效果
    在这里插入图片描述
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值