Vulkan(0)搭建环境-清空窗口
Vulkan(0)搭建环境-清空窗口
认识Vulkan
Vulkan是新一代3D图形API,它继承了OpenGL的优点,弥补了OpenGL的缺憾。有点像科创板之于主板,歼20之于歼10,微信之于QQ,网店之于实体店,今日之于昨日。
使用OpenGL时,每次drawcall都需要向OpenGL提交很多数据。而Vulkan可以提前将这些drawcall指令保存到一个buffer(像保存顶点数据到buffer一样),这样就减少了很多开销。
使用OpenGL时,OpenGL的Context会包含很多你并不打算使用的东西,例如线的宽度、混合等。而Vulkan不会提供这些你用不到的东西,你需要什么,你来指定。(当然,你不指定,Vulkan不会自动地提供)
Vulkan还支持多线程,OpenGL这方面就不行了。
Vulkan对GPU的抽象比OpenGL更加细腻。
搭建环境
本文和本系列都将使用C#和Visual Studio 2017来学习使用Vulkan。
首先,在官网(https://vulkan.lunarg.com)下载vulkan-sdk.exe和vulkan-runtime.exe。完后安装。vulkan-runtime.exe也可以在(https://files.cnblogs.com/files/bitzhuwei/vulkan-runtime.rar)下载。vulkan-sdk.exe太大,我就不提供下载了。
然后,下载Vulkan.net库(https://github.com/bitzhuwei/Vulkan.net)。这是本人搜罗整理来的一个Vulkan库,外加一些示例代码。用VS2017打开Vulkan.sln,在这个解决方案下就可以学习使用Vulkan了。
如果读者在Github上的下载速度太慢,可以试试将各个文件单独点开下载。这很笨,但也是个办法。
简单介绍下此解决方案。
Vulkan文件夹下的Vulkan.csproj是对Vulkan API的封装。Vulkan使用了大量的struct、enum,这与OpenGL类似。
Vulkan.Platforms文件夹下的Vulkan.Platforms.csproj是平台相关的一些API。
Lesson01Clear文件夹下的是第一个示例,展示了Vulkan清空窗口的代码。以后会逐步添加更多的示例。
有了这个库,读者就可以运行示例程序,一点点地读代码,慢慢理解Vulkan了。这也是本人用的最多的学习方法。遇到不懂的就上网搜索,毕竟我没有别人可以问。
这个库还很不成熟,以后会有大的改动。但这不妨碍学习,反而是学习的好资料,在变动的过程中方能体会软件工程的精髓。
清空窗口
用Vulkan写个清空窗口的程序,就像是用C写个hello world。
外壳
新建Windows窗体应用程序。
添加对类库Vulkan和Vulkan.Platforms的引用:
添加此项目的核心类型LessonClear。Vulkan需要初始化(Init)一些东西,在每次渲染时,渲染(Render)一些东西。
- namespace Lesson01Clear {
- unsafe class LessonClear {
- bool isInitialized = false;
- public void Init() {
- if (this.isInitialized) { return; }
- this.isInitialized = true;
- }
- public void Render() {
- if (!isInitialized) return;
- }
- }
- }
添加一个User Control,用以调用LessonClear。
- namespace Lesson01Clear {
- public partial class UCClear : UserControl {
- LessonClear lesson;
- public UCClear() {
- InitializeComponent();
- }
- protected override void OnLoad(EventArgs e) {
- base.OnLoad(e);
- this.lesson = new LessonClear();
- this.lesson.Init();
- }
- protected override void OnPaintBackground(PaintEventArgs e) {
- var lesson = this.lesson;
- if (lesson != null) {
- lesson.Render();
- }
- }
- }
- }
在主窗口中添加一个自定义控件UCClear。这样,在窗口启动时,就会自动执行LessonClear的初始化和渲染功能了。
此时的解决方案如下:
初始化
要初始化的东西比较多,我们一项一项来看。
VkInstance
在LessonClear中添加成员变量VkInstance vkIntance,在InitInstance()函数中初始化它。
- unsafe class LessonClear {
- VkInstance vkIntance;
- bool isInitialized = false;
- public void Init() {
- if (this.isInitialized) { return; }
- this.vkIntance = InitInstance();
- this.isInitialized = true;
- }
- private VkInstance InitInstance() {
- VkLayerProperties[] layerProperties;
- Layer.EnumerateInstanceLayerProperties(out layerProperties);
- string[] layersToEnable = layerProperties.Any(l => StringHelper.ToStringAnsi(l.LayerName) == "VK_LAYER_LUNARG_standard_validation")
- ? new[] { "VK_LAYER_LUNARG_standard_validation" }
- : new string[];
- var appInfo = new VkApplicationInfo();
- {
- appInfo.SType = VkStructureType.ApplicationInfo;
- uint version = Vulkan.Version.Make(, , );
- appInfo.ApiVersion = version;
- }
- var extensions = new string[] { "VK_KHR_surface", "VK_KHR_win32_surface", "VK_EXT_debug_report" };
- var info = new VkInstanceCreateInfo();
- {
- info.SType = VkStructureType.InstanceCreateInfo;
- extensions.Set(ref info.EnabledExtensionNames, ref info.EnabledExtensionCount);
- layersToEnable.Set(ref info.EnabledLayerNames, ref info.EnabledLayerCount);
- info.ApplicationInfo = (IntPtr)(&appInfo);
- }
- VkInstance result;
- VkInstance.Create(ref info, null, out result).Check();
- return result;
- }
- }
VkInstance的extension和layer是什么,一时难以说清,先不管。VkInstance像是一个缓存,它根据用户提供的参数,准备好了用户可能要用的东西。在创建VkInstance时,我明显感到程序卡顿了1秒。如果用户稍后请求的东西在缓存中,VkInstance就立即提供给他;如果不在,VkInstance就不给,并抛出VkResult。
以“Vk”开头的一般是Vulkan的结构体,或者对某种Vulkan对象的封装。
VkInstance就是一个对Vulkan对象的封装。创建一个VkInstance对象时,Vulkan的API只会返回一个 IntPtr 指针。在本库中,用一个class VkInstance将其封装起来,以便使用。
创建一个VkInstance对象时,需要我们提供给Vulkan API一个对应的 VkInstanceCreateInfo 结构体。这个结构体包含了创建VkInstance所需的各种信息,例如我们想让这个VkInstance支持哪些extension、哪些layer等。对于extension,显然,这必须用一个数组指针IntPtr和extension的总数来描述。
- public struct VkInstanceCreateInfo {
- public VkStructureType SType;
- public IntPtr Next;
- public UInt32 Flags;
- public IntPtr ApplicationInfo;
- public UInt32 EnabledLayerCount;
- public IntPtr EnabledLayerNames;
- public UInt32 EnabledExtensionCount; // 数组元素的数量
- public IntPtr EnabledExtensionNames; // 数组指针
- }
这样的情况在Vulkan十分普遍,所以本库提供一个扩展方法来执行这一操作:
- /// <summary>
- /// Set an array of structs to specified <paramref name="target"/> and <paramref name="count"/>.
- /// <para>Enumeration types are not allowed to use this method.
- /// If you have to, convert them to byte/short/ushort/int/uint according to their underlying types first.</para>
- /// </summary>
- /// <param name="value"></param>
- /// <param name="target">address of first element/array.</param>
- /// <param name="count">How many elements?</param>
- public static void Set<T>(this T[] value, ref IntPtr target, ref UInt32 count) where T : struct {
- { // free unmanaged memory.
- if (target != IntPtr.Zero) {
- Marshal.FreeHGlobal(target);
- target = IntPtr.Zero;
- count = ;
- }
- }
- {
- count = (UInt32)value.Length;
- int elementSize = Marshal.SizeOf<T>();
- int byteLength = (int)(count * elementSize);
- IntPtr array = Marshal.AllocHGlobal(byteLength);
- var dst = (byte*)array;
- GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned);
- IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(value, );
- var src = (byte*)address;
- for (int i = ; i < byteLength; i++) {
- dst[i] = src[i];
- }
- pin.Free();
- target = array;
- }
- }
这个Set<T>()函数的核心作用是:在非托管内存上创建一个数组,将托管内存中的数组T[] value中的数据复制过去,然后,记录非托管内存中的数组的首地址(target)和元素数量(count)。当然,如果这不是第一次让target记录非托管内存中的某个数组,那就意味着首先应当将target指向的数组释放掉。
如果这里的T是枚举类型, Marshal.SizeOf() 会抛出异常,所以,必须先将枚举数组转换为 byte/short/ushort/int/uint 类型的数组。至于Marshal.SizeOf为什么会抛异常,我也不知道。
如果这里的T是string,那么必须用另一个变种函数代替:
- /// <summary>
- /// Set an array of strings to specified <paramref name="target"/> and <paramref name="count"/>.
- /// </summary>
- /// <param name="value"></param>
- /// <param name="target">address of first element/array.</param>
- /// <param name="count">How many elements?</param>
- public static void Set(this string[] value, ref IntPtr target, ref UInt32 count) {
- { // free unmanaged memory.
- var pointer = (IntPtr*)(target.ToPointer());
- if (pointer != null) {
- for (int i = ; i < count; i++) {
- Marshal.FreeHGlobal(pointer[i]);
- }
- }
- }
- {
- int length = value.Length;
- if (length > ) {
- int elementSize = Marshal.SizeOf(typeof(IntPtr));
- int byteLength = (int)(length * elementSize);
- IntPtr array = Marshal.AllocHGlobal(byteLength);
- IntPtr* pointer = (IntPtr*)array.ToPointer();
- for (int i = ; i < length; i++) {
- IntPtr str = Marshal.StringToHGlobalAnsi(value[i]);
- pointer[i] = str;
- }
- target = array;
- }
- count = (UInt32)length;
- }
- }
public static void Set(this string[] value, ref IntPtr target, ref UInt32 count)
实现和解释起来略显复杂,但使用起来十分简单:
- var extensions = new string[] { "VK_KHR_surface", "VK_KHR_win32_surface", "VK_EXT_debug_report" };
- extensions.Set(ref info.EnabledExtensionNames, ref info.EnabledExtensionCount);
- var layersToEnable = new[] { "VK_LAYER_LUNARG_standard_validation" };
- layersToEnable.Set(ref info.EnabledLayerNames, ref info.EnabledLayerCount);
在后续创建其他Vulkan对象时,我们将多次使用这一方法。
创建VkInstance的内部过程,就是调用Vulkan API的问题:
- namespace Vulkan {
- public unsafe partial class VkInstance : IDisposable {
- public readonly IntPtr handle;
- private readonly UnmanagedArray<VkAllocationCallbacks> callbacks;
- public static VkResult Create(ref VkInstanceCreateInfo createInfo, UnmanagedArray<VkAllocationCallbacks> callbacks, out VkInstance instance) {
- VkResult result = VkResult.Success;
- var handle = new IntPtr();
- VkAllocationCallbacks* pAllocator = callbacks != null ? (VkAllocationCallbacks*)callbacks.header : null;
- fixed (VkInstanceCreateInfo* pCreateInfo = &createInfo) {
- vkAPI.vkCreateInstance(pCreateInfo, pAllocator, &handle).Check();
- }
- instance = new VkInstance(callbacks, handle);
- return result;
- }
- private VkInstance(UnmanagedArray<VkAllocationCallbacks> callbacks, IntPtr handle) {
- this.callbacks = callbacks;
- this.handle = handle;
- }
- public void Dispose() {
- VkAllocationCallbacks* pAllocator = callbacks != null ? (VkAllocationCallbacks*)callbacks.header : null;
- vkAPI.vkDestroyInstance(this.handle, pAllocator);
- }
- }
- class vkAPI {
- const string VulkanLibrary = "vulkan-1";
- [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
- internal static unsafe extern VkResult vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, IntPtr* pInstance);
- [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
- internal static unsafe extern void vkDestroyInstance(IntPtr instance, VkAllocationCallbacks* pAllocator);
- }
- }
在 public static VkResult Create(ref VkInstanceCreateInfo createInfo, UnmanagedArray<VkAllocationCallbacks> callbacks, out VkInstance instance); 函数中:
第一个参数用ref标记,是因为这样就会强制程序员提供一个 VkInstanceCreateInfo 结构体。如果改用 VkInstanceCreateInfo* ,那么程序员就有可能提供一个null指针,这对于Vulkan API的 vkCreateInstance() 是没有应用意义的。
对第二个参数提供null指针是有应用意义的,但是,如果用 VkAllocationCallbacks* ,那么此参数指向的对象仍旧可能位于托管内存中(从而,在后续阶段,其位置有可能被GC改变)。用 UnmanagedArray<VkAllocationCallbacks> 就可以保证它位于非托管内存。
对于第三个参数,之所以让它用out标记(而不是放到返回值上),是因为 vkCreateInstance() 的返回值是 VkResult 。这样写,可以保持代码的风格与Vulkan一致。如果以后需要用切面编程之类的的方式添加log等功能,这样的一致性就会带来便利。
在函数中声明的结构体变量(例如这里的 var handle = new IntPtr(); ),可以直接取其地址( &handle )。
创建VkInstance的方式方法流程,与创建其他Vulkan对象的方式方法流程是极其相似的。读者可以触类旁通。
VkSurfaceKhr
在LessonClear中添加成员变量VkSurfaceKhr vkSurface,在InitSurface()函数中初始化它。
- namespace Lesson01Clear {
- unsafe class LessonClear {
- VkInstance vkIntance;
- VkSurfaceKhr vkSurface;
- bool isInitialized = false;
- public void Init(IntPtr hwnd, IntPtr processHandle) {
- if (this.isInitialized) { return; }
- this.vkIntance = InitInstance();
- this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
- this.isInitialized = true;
- }
- private VkSurfaceKhr InitSurface(VkInstance instance, IntPtr hwnd, IntPtr processHandle) {
- var info = new VkWin32SurfaceCreateInfoKhr {
- SType = VkStructureType.Win32SurfaceCreateInfoKhr,
- Hwnd = hwnd, // handle of User Control.
- Hinstance = processHandle, //Process.GetCurrentProcess().Handle
- };
- return instance.CreateWin32SurfaceKHR(ref info, null);
- }
- }
- }
可见,VkSurfaceKhr的创建与VkInstance遵循同样的模式,只是CreateInfo内容比较少。VkSurfaceKhr需要知道窗口句柄和进程句柄,这样它才能渲染到相应的窗口/控件上。
VkPhysicalDevice
这里的物理设备指的就是我们的计算机上的GPU了。
- namespace Lesson01Clear {
- unsafe class LessonClear {
- VkInstance vkIntance;
- VkSurfaceKhr vkSurface;
- VkPhysicalDevice vkPhysicalDevice;
- bool isInitialized = false;
- public void Init(IntPtr hwnd, IntPtr processHandle) {
- if (this.isInitialized) { return; }
- this.vkIntance = InitInstance();
- this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
- this.vkPhysicalDevice = InitPhysicalDevice();
- this.isInitialized = true;
- }
- private VkPhysicalDevice InitPhysicalDevice() {
- VkPhysicalDevice[] physicalDevices;
- this.vkIntance.EnumeratePhysicalDevices(out physicalDevices);
- return physicalDevices[];
- }
- }
- }
创建VkPhysicalDivice对象不需要Callback:
- namespace Vulkan {
- public unsafe partial class VkPhysicalDevice {
- public readonly IntPtr handle;
- public static VkResult Enumerate(VkInstance instance, out VkPhysicalDevice[] physicalDevices) {
- if (instance == null) { physicalDevices = null; return VkResult.Incomplete; }
- UInt32 count;
- VkResult result = vkAPI.vkEnumeratePhysicalDevices(instance.handle, &count, null).Check();
- var handles = stackalloc IntPtr[(int)count];
- if (count > ) {
- result = vkAPI.vkEnumeratePhysicalDevices(instance.handle, &count, handles).Check();
- }
- physicalDevices = new VkPhysicalDevice[count];
- for (int i = ; i < count; i++) {
- physicalDevices[i] = new VkPhysicalDevice(handles[i]);
- }
- return result;
- }
- private VkPhysicalDevice(IntPtr handle) {
- this.handle = handle;
- }
- }
- }
在函数中声明的变量(例如这里的 var handle = new IntPtr(); ),可以直接取其地址( &handle )。
但是在函数中声明的数组,数组本身是在堆中的,不能直接取其地址。为了能够取其地址,可以用( var handles = stackalloc IntPtr[(int)count]; )这样的方式,这会将数组本身创建到函数自己的栈空间,从而可以直接取其地址了。
VkDevice
这个设备是对物理设备的缓存\抽象\接口,我们想使用物理设备的哪些功能,就在CreateInfo中指定,然后创建VkDevice。(不指定的功能,以后就无法使用。)后续各种对象,都是用VkDevice创建的。
- namespace Lesson01Clear {
- unsafe class LessonClear {
- VkInstance vkIntance;
- VkSurfaceKhr vkSurface;
- VkPhysicalDevice vkPhysicalDevice;
- VkDevice vkDevice;
- bool isInitialized = false;
- public void Init(IntPtr hwnd, IntPtr processHandle) {
- if (this.isInitialized) { return; }
- this.vkIntance = InitInstance();
- this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
- this.vkPhysicalDevice = InitPhysicalDevice();
- VkSurfaceFormatKhr surfaceFormat = SelectFormat(this.vkPhysicalDevice, this.vkSurface);
- VkSurfaceCapabilitiesKhr surfaceCapabilities;
- this.vkPhysicalDevice.GetSurfaceCapabilitiesKhr(this.vkSurface, out surfaceCapabilities);
- this.vkDevice = InitDevice(this.vkPhysicalDevice, this.vkSurface);
- this.isInitialized = true;
- }
- private VkDevice InitDevice(VkPhysicalDevice physicalDevice, VkSurfaceKhr surface) {
- VkQueueFamilyProperties[] properties = physicalDevice.GetQueueFamilyProperties();
- uint index;
- for (index = ; index < properties.Length; ++index) {
- VkBool32 supported;
- physicalDevice.GetSurfaceSupportKhr(index, surface, out supported);
- if (!supported) { continue; }
- if (properties[index].QueueFlags.HasFlag(VkQueueFlags.QueueGraphics)) break;
- }
- var queueInfo = new VkDeviceQueueCreateInfo();
- {
- queueInfo.SType = VkStructureType.DeviceQueueCreateInfo;
- new float[] { 1.0f }.Set(ref queueInfo.QueuePriorities, ref queueInfo.QueueCount);
- queueInfo.QueueFamilyIndex = index;
- }
- var deviceInfo = new VkDeviceCreateInfo();
- {
- deviceInfo.SType = VkStructureType.DeviceCreateInfo;
- new string[] { "VK_KHR_swapchain" }.Set(ref deviceInfo.EnabledExtensionNames, ref deviceInfo.EnabledExtensionCount);
- new VkDeviceQueueCreateInfo[] { queueInfo }.Set(ref deviceInfo.QueueCreateInfos, ref deviceInfo.QueueCreateInfoCount);
- }
- VkDevice device;
- physicalDevice.CreateDevice(ref deviceInfo, null, out device);
- return device;
- }
- }
- }
后续的Queue、Swapchain、Image、RenderPass、Framebuffer、Fence和Semaphore等都不再一一介绍,毕竟都是十分类似的创建过程。
最后只介绍一下VkCommandBuffer。
VkCommandBuffer
Vulkan可以将很多渲染指令保存到buffer,将buffer一次性上传到GPU内存,这样以后每次调用它即可,不必重复提交这些数据了。
- namespace Lesson01Clear {
- unsafe class LessonClear {
- VkInstance vkIntance;
- VkSurfaceKhr vkSurface;
- VkPhysicalDevice vkPhysicalDevice;
- VkDevice vkDevice;
- VkQueue vkQueue;
- VkSwapchainKhr vkSwapchain;
- VkImage[] vkImages;
- VkRenderPass vkRenderPass;
- VkFramebuffer[] vkFramebuffers;
- VkFence vkFence;
- VkSemaphore vkSemaphore;
- VkCommandBuffer[] vkCommandBuffers;
- bool isInitialized = false;
- public void Init(IntPtr hwnd, IntPtr processHandle) {
- if (this.isInitialized) { return; }
- this.vkIntance = InitInstance();
- this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
- this.vkPhysicalDevice = InitPhysicalDevice();
- VkSurfaceFormatKhr surfaceFormat = SelectFormat(this.vkPhysicalDevice, this.vkSurface);
- VkSurfaceCapabilitiesKhr surfaceCapabilities;
- this.vkPhysicalDevice.GetSurfaceCapabilitiesKhr(this.vkSurface, out surfaceCapabilities);
- this.vkDevice = InitDevice(this.vkPhysicalDevice, this.vkSurface);
- this.vkQueue = this.vkDevice.GetDeviceQueue(, );
- this.vkSwapchain = CreateSwapchain(this.vkDevice, this.vkSurface, surfaceFormat, surfaceCapabilities);
- this.vkImages = this.vkDevice.GetSwapchainImagesKHR(this.vkSwapchain);
- this.vkRenderPass = CreateRenderPass(this.vkDevice, surfaceFormat);
- this.vkFramebuffers = CreateFramebuffers(this.vkDevice, this.vkImages, surfaceFormat, this.vkRenderPass, surfaceCapabilities);
- var fenceInfo = new VkFenceCreateInfo() { SType = VkStructureType.FenceCreateInfo };
- this.vkFence = this.vkDevice.CreateFence(ref fenceInfo);
- var semaphoreInfo = new VkSemaphoreCreateInfo() { SType = VkStructureType.SemaphoreCreateInfo };
- this.vkSemaphore = this.vkDevice.CreateSemaphore(ref semaphoreInfo);
- this.vkCommandBuffers = CreateCommandBuffers(this.vkDevice, this.vkImages, this.vkFramebuffers, this.vkRenderPass, surfaceCapabilities);
- this.isInitialized = true;
- }
- VkCommandBuffer[] CreateCommandBuffers(VkDevice device, VkImage[] images, VkFramebuffer[] framebuffers, VkRenderPass renderPass, VkSurfaceCapabilitiesKhr surfaceCapabilities) {
- var createPoolInfo = new VkCommandPoolCreateInfo {
- SType = VkStructureType.CommandPoolCreateInfo,
- Flags = VkCommandPoolCreateFlags.ResetCommandBuffer
- };
- var commandPool = device.CreateCommandPool(ref createPoolInfo);
- var commandBufferAllocateInfo = new VkCommandBufferAllocateInfo {
- SType = VkStructureType.CommandBufferAllocateInfo,
- Level = VkCommandBufferLevel.Primary,
- CommandPool = commandPool.handle,
- CommandBufferCount = (uint)images.Length
- };
- VkCommandBuffer[] buffers = device.AllocateCommandBuffers(ref commandBufferAllocateInfo);
- for (int i = ; i < images.Length; i++) {
- var commandBufferBeginInfo = new VkCommandBufferBeginInfo() {
- SType = VkStructureType.CommandBufferBeginInfo
- };
- buffers[i].Begin(ref commandBufferBeginInfo);
- {
- var renderPassBeginInfo = new VkRenderPassBeginInfo();
- {
- renderPassBeginInfo.SType = VkStructureType.RenderPassBeginInfo;
- renderPassBeginInfo.Framebuffer = framebuffers[i].handle;
- renderPassBeginInfo.RenderPass = renderPass.handle;
- new VkClearValue[] { new VkClearValue { Color = new VkClearColorValue(0.9f, 0.7f, 0.0f, 1.0f) } }.Set(ref renderPassBeginInfo.ClearValues, ref renderPassBeginInfo.ClearValueCount);
- renderPassBeginInfo.RenderArea = new VkRect2D {
- Extent = surfaceCapabilities.CurrentExtent
- };
- };
- buffers[i].CmdBeginRenderPass(ref renderPassBeginInfo, VkSubpassContents.Inline);
- {
- // nothing to do in this lesson.
- }
- buffers[i].CmdEndRenderPass();
- }
- buffers[i].End();
- }
- return buffers;
- }
- }
- }
本例中的VkClearValue用于指定背景色,这里指定了黄色,运行效果如下:
总结
如果看不懂本文,就去看代码,运行代码,再来看本文。反反复复看,总会懂。
Vulkan(0)搭建环境-清空窗口的更多相关文章
- Windows 7旗舰版搭建andriod 4.0开发环境记录
搭建Android环境步骤(仅供参考): 官方搭建步骤: http://developer.android.com/index.html 搭建环境之前需要下载下面几个文件包: 一.安装Java运行环境 ...
- android 5.0开发环境搭建
Android 5.0 是 Google 于 2014 年 10 月 15 日发布的全新 Android 操作系统.本文将就最新的Android 5.0 开发环境搭建做详细介绍. 工具/原料 jdk- ...
- selenium win7+selenium2.0+python环境搭建
win7+selenium2.0+python环境搭建 by:授客 QQ:1033553122 步骤1:下载python 担心最新版的支持不太好,这里我下载的是python 2.7(selenium之 ...
- EJB3.0开发环境的搭建
EJB Container的介绍SUN公司正式推出了EJB的规范之后,在众多的公司和开发者中引起了非常大的反响.标志着用Java开发企业级应用系统将变的非常easy.很多公司都已经推出了或正打算EJB ...
- windows下cocos2dx3.0开发环境及Android编译环境搭建
cocos2dx更新到了3.x版本号,自己一直没有换,如今开发组要求统一换版本号,我就把搭建好开发环境的过程记录下来. 一.Windowns下开发环境搭建 1. 所需工具 1)coc ...
- Jira 6.0.5环境搭建
敏捷开发-Jira 6.0.5环境搭建[1] 我的环境 Win7 64位,MSSql2008 R2,已经安装tomcat了 拓展环境 jira 6.0.5 百度网盘下载 ...
- Vulkan Tutorial 开发环境搭建之Windows
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 相信很多人在开始学习Vulkan开发的起始阶段都会在开发环境的配置上下一些功夫,那么 ...
- Hadoop-2.8.0 开发环境搭建(Mac)
Hadoop是一个由Apache基金会开发的分布式系统架构,简称HDFS,具有高容错性.可伸缩性等特点,并且可以部署在低配置的硬件上:同时,提供了高吞吐量的数据访问性能,适用于超大数据集的应用程序,以 ...
- odoo10.0在odoo12.0环境的基础上搭建环境
在前边的文章中,讲述了如何搭建12.0的环境,现由业务的需要需要在此基础上搭建基于python2.7的10.0版本. 第一步,安装python2.7 sudo apt- 第二步,安装python-de ...
随机推荐
- 高级MySQL
一.MySQL的架构介绍 1.高级MySQL MySQL内核 SQL优化 MySQL服务器的优化 各种参数常亮设定 查询语句优化 主从复制 软硬件升级 容灾备份 SQL编程 2.MySQL的Linux ...
- Python解释器安装教程和环境变量配置
Python解释器安装教程和环境变量配置 Python解释器安装 登录Python的官方网站 https://www.python.org/ 进行相应版本的下载. 第一步:根据电脑系统选择软件适 ...
- 10-Helm
Helm Chart仓库 helm 架构 https://helm.sh/docs/architecture/ 主要概念 chart 创建Kubernetes应用程序实例所必需的一组信息 config ...
- T-SQL 镜像测试
--====================================================== ----镜像计划建立 2016-05-10 17:05:16.463 hubiyun ...
- 警惕!CAF效应导致PCB漏电
最近碰到一个PCB漏电的问题,起因是一款低功耗产品,本来整机uA级别的电流,常温老化使用了一段时间后发现其功耗上升,个别样机功耗甚至达到了mA级别.仔细排除了元器件问题,最终发现了一个5V电压点,在产 ...
- 分布式ID系列之为什么需要分布式ID以及生成分布式ID的业务需求
为什么需要分布式id生成系统 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店.猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID ...
- solidity智能合约字节数最大值及缩减字节数
智能合约最大字节数 在Solidity中,EIP 170将contract的最大大小限制为24 KB .因此,如果智能合约内容过多,会导致无法进行发布操作. 减少压缩字节数方法 方法及变量命名 在一定 ...
- Lua语言学习
1,语法 语句不用分号结尾 function ... end if .. else .. end 2, io库, string库, table库, OS库, 算术库, debug库 3, dofile ...
- Oracle创建设置查询权限用户
用户创建的可以参考博客: https://blog.csdn.net/u014427391/article/details/84889023 Oracle授权表权限给用户: 语法:grant [权限名 ...
- Linux curl 表单登录或提交与cookie使用
本文主要讲解通过curl 实现表单提交登录.单独的表单提交与表单登录都差不多,因此就不单独说了. 说明:针对curl表单提交实现登录,不是所有网站都适用,原因是有些网站后台做了限制或有其他校验.我们不 ...