DX12 之 miniEngine 代码自解

本贴最后更新于 2284 天前,其中的信息可能已经时移俗易

好像有个叫 Vulkan 的图形 API 试图替代 Opengl,不过要跟上时代,还是得把 WIN10 装上,试试看在 DX12 写个下个时代的 3D 设计工具,当然只是个小玩意,不过据说很多软件能走到今天,也是因为积淀,如果不早点开始积淀,就永远会落后那么多
我想给这个玩意起个名字,叫啥呢,就叫 W4D 好了,首先这个软件定位不是游戏引擎,而且是超轻量的,W:windows 4:x y z t D: Data 指代在 windows 下处理 3 维与时间数据的软件
想要有什么呢?
1:首先需要能应对基本的 3D 建模,绑骨,展 UV 贴图,动作
2:要有语言能应对复杂的各种情况 类似 maxscript 脚本语言,但是这里可能级别要更强
3:最好有时间轴,觉得这个概念与 2D 里 flash 接近,跟 unity 里 animation 差不多,用不着搞成 timeline 那么复杂。
代码使用 C++ DX,确定要一切考虑效率,即使函数入口都不要检查,只在最外层进行检查,出错是程序员的事,代码不是用来为程序员负责的,希望这个玩意能像刀片一样 欻欻的就搞定哈哈

环境 WIN10,VS2017.这二者搞定后,微软官方例子可以直接打开编译运行的,miniEngine 可能要重定向项目-> 选择相应的 SDK 版本(在解决方案右键会出来选项)

dx12 基础
http://www.rastertek.com/tutdx12.html
官网的
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899121(v=vs.85).aspx

Working samples

例子
Working samples (in the form of Visual Studio 2015 projects) can be downloaded from GitHub/Microsoft/DirectX-Graphics-Samples.

DX12 遍历显卡
Intel(R) HD Graphics 4600
NVIDIA GeForce GTX 960M
Microsoft Basic Render Driver

把特性设置为 D3D_FEATURE_LEVEL_11_0

HRESULT hr = CreateDXGIFactory(__uuidof(IDXGIFactory4), (void**)&factory);
int i = 0;
IDXGIAdapter * pAdapter;
std::vector <IDXGIAdapter*> vAdapters;
while (factory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND)
{
//hr = D3D12CreateDevice(pAdapter, feature_level, __uuidof(ID3D12Device), (void**)& pDevice);
//if (SUCCEEDED(hr))
// break;
pAdapter->GetDesc(&adapterDesc);
wcstombs_s(&stringLength, (char*)videoCardDes, 128, adapterDesc.Description, 128);
vAdapters.push_back(pAdapter);
++i;
}

http://www.rastertek.com/
这个网站上 3 篇 DX12 的已经照着写了一遍,感觉概念多,复杂,勉强能跑个背景色出来就废了这么大功夫,可惜的是作者似乎没有继续写了
还是看 dx12 在 git 上的例子好了。

哎呀,感觉好难啊。
打开微软 ModelViewer_VS15.sln 的例子,运行效果还是挺好的,就是连入口都摸了半天
场景示例

入口是一个宏
CREATE_APPLICATION( ModelViewer )

映射到 GameCore::RunApplication( app_class(), L#app_class );
namespace GameCore 里定义了这个全局函数 RunApplication
这个函数初始化了引擎的各个部分,注册了窗口类,创建了消息循环,循环结束后,进行各个部分的资源清理,基本所有引擎框架都有类似这样的一个结构吧。
不过这个函数的入口是 c 语言的 int wmain(int argc, wchar_t** argv),估计是要通过宏适应多平台吧。

在 GameCore::RunApplication 里看下有哪些模块都在这里初始化的
在创建完窗口后调用了
InitializeApplication(app);
定义为
void InitializeApplication( IGameApp& game )
{
Graphics::Initialize();
SystemTime::Initialize();
GameInput::Initialize();
EngineTuning::Initialize();

game.Startup();

}
这个 game 参数,就是 ModelViewer 的一个对象

这部分基本和
www.rastertek.com
描述的差不多,图形初始化,输入初始化,游戏初始化,系统时间初始化,引擎协调初始化
Graphics::Initialize();在这里,
创建了 dx12 设备
(遍历了一次取了显存最大的显卡,还过滤掉了软件模拟卡)

SIZE_T MaxSize = 0;

for (uint32_t Idx = 0; DXGI_ERROR_NOT_FOUND != dxgiFactory->EnumAdapters1(Idx, &pAdapter); ++Idx)
{
DXGI_ADAPTER_DESC1 desc;
pAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;

if (desc.DedicatedVideoMemory > MaxSize && SUCCEEDED(D3D12CreateDevice(pAdapter.Get(), D3D_FEATURE_LEVEL_11_0, MY_IID_PPV_ARGS(&pDevice)))) { pAdapter->GetDesc1(&desc); Utility::Printf(L"D3D12-capable hardware found: %s (%u MB)\n", desc.Description, desc.DedicatedVideoMemory >> 20); MaxSize = desc.DedicatedVideoMemory; }

}

if (MaxSize > 0)
g_Device = pDevice.Detach();

然后检查了设备是否支持 R11G11B10 和 R16G16B16A16 2 种 UAVLoadSupport 上传格式,这部分仅仅设置了几个变量标识是否有此特性(g_bTypedUAVLoadSupport_R11G11B10_FLOAT,g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT)。

D3D12_FEATURE_DATA_D3D12_OPTIONS FeatureData = {};
if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &FeatureData, sizeof(FeatureData))))
{
if (FeatureData.TypedUAVLoadAdditionalFormats)
{
D3D12_FEATURE_DATA_FORMAT_SUPPORT Support =
{
DXGI_FORMAT_R11G11B10_FLOAT, D3D12_FORMAT_SUPPORT1_NONE, D3D12_FORMAT_SUPPORT2_NONE
};

if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) && (Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0) { g_bTypedUAVLoadSupport_R11G11B10_FLOAT = true; } Support.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) && (Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0) { g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT = true; } }

}

接着创建了各种 commandqueue,
g_CommandManager.Create(g_Device);
g_CommandManager 是 CommandListManager 类型的
跟进去看一下
void CommandListManager::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);

m_Device = pDevice; m_GraphicsQueue.Create(pDevice); m_ComputeQueue.Create(pDevice); m_CopyQueue.Create(pDevice);

}

m_GraphicsQueue m_ComputeQueue m_CopyQueue 都是 CommandQueue,这里的实现都一样

void CommandQueue::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);
ASSERT(!IsReady());
ASSERT(m_AllocatorPool.Size() == 0);

D3D12_COMMAND_QUEUE_DESC QueueDesc = {}; QueueDesc.Type = m_Type; QueueDesc.NodeMask = 1; pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue)); m_CommandQueue->SetName(L"CommandListManager::m_CommandQueue"); ASSERT_SUCCEEDED(pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, MY_IID_PPV_ARGS(&m_pFence))); m_pFence->SetName(L"CommandListManager::m_pFence"); m_pFence->Signal((uint64_t)m_Type << 56); m_FenceEventHandle = CreateEvent(nullptr, false, false, nullptr); ASSERT(m_FenceEventHandle != INVALID_HANDLE_VALUE); m_AllocatorPool.Create(pDevice); ASSERT(IsReady());

}

这个类应该算对 dx12 commandqueue 的封装吧,核心就是使用 pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue));
然后创建了 fence,fence_event
m_AllocatorPool.Create(pDevice);只是把类的成员变量设置为 pDevice,这个 m_AllocatorPool 也是对 commandAllocator 的一个池的封装 他成员里有 std::vector<ID3D12CommandAllocator*> m_AllocatorPool;
这部分完了,返回堆栈上面的 Graphics::Initialize();

在 g_CommandManager.Create(g_Device);之后

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = g_DisplayWidth;
swapChainDesc.Height = g_DisplayHeight;
swapChainDesc.Format = SwapChainFormat;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) // Win32
ASSERT_SUCCEEDED(dxgiFactory->CreateSwapChainForHwnd(g_CommandManager.GetCommandQueue(), GameCore::g_hWnd, &swapChainDesc, nullptr, nullptr, &s_SwapChain1));

创建了一个交换链的描述,然后通过这个描述(swapChainDesc)创建了一个交换链,g_CommandManager.GetCommandQueue()返回的是他成员里,m_GraphicsQueue 创建的 CommandQueue, 交换链接口的指针放到 s_SwapChain1 里
下面有部分代码不明白意思,跳过去

#if CONDITIONALLY_ENABLE_HDR_OUTPUT && defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2)
{
IDXGISwapChain4* swapChain = (IDXGISwapChain4*)s_SwapChain1;
ComPtr output;
ComPtr output6;
DXGI_OUTPUT_DESC1 outputDesc;
UINT colorSpaceSupport;

// Query support for ST.2084 on the display and set the color space accordingly if (SUCCEEDED(swapChain->GetContainingOutput(&output)) && SUCCEEDED(output.As(&output6)) && SUCCEEDED(output6->GetDesc1(&outputDesc)) && outputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 && SUCCEEDED(swapChain->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, &colorSpaceSupport)) && (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) && SUCCEEDED(swapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020))) { g_bEnableHDROutput = true; }

}
#endif
看英文解释是是否支持 ST.2048 并设置交换链的颜色空间的。

接着看
for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}

这部分是创建交换链使用的资源以及相应的 RenderTargetView,一个交换链上创建数个 RTV,话说这个包装还是有点难理解的。
交换链上 GetBuffer 类似按照指定的序号(在这里就是 i),把 ID3D12Resource 指针返回,要使用就创建一个 ID3D12Resource 描述的 RTV 对象
这个 RTV 对象是通过
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach())创建的
g_DisplayPlane 是一个 ColorBuffer 数组
跟进去看一下

void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);

//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); //Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]); m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);

}

再跟进
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);

ColorBuffer 继承 PixelBuffer,跳转到这里

void PixelBuffer::AssociateWithResource( ID3D12Device* Device, const std::wstring& Name, ID3D12Resource* Resource, D3D12_RESOURCE_STATES CurrentState )
{
(Device); // Unused until we support multiple adapters

ASSERT(Resource != nullptr); D3D12_RESOURCE_DESC ResourceDesc = Resource->GetDesc(); m_pResource.Attach(Resource); m_UsageState = CurrentState; m_Width = (uint32_t)ResourceDesc.Width; // We don't care about large virtual textures yet m_Height = ResourceDesc.Height; m_ArraySize = ResourceDesc.DepthOrArraySize; m_Format = ResourceDesc.Format;

#ifndef RELEASE
m_pResource->SetName(Name.c_str());
#else
(Name);
#endif
}

这个函数只是调用 ID312DResources 指针获取了资源描述 GetDesc(),然后把这些描述的属性记在了自己的成员里
跳出 AssociateWithResource 函数返回
void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);//跳出堆栈

//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); //Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]); //到这里 m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);

}

m_RTVHandle 大概是一个 RTV 对象描述 的句柄。
跟进 Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);内部
inline D3D12_CPU_DESCRIPTOR_HANDLE AllocateDescriptor( D3D12_DESCRIPTOR_HEAP_TYPE Type, UINT Count = 1 )
{
return g_DescriptorAllocator[Type].Allocate(Count);
}
实际上最后是用 g_DescriptorAllocator[Type].Allocate(Count)返回了一个句柄,看下 g_DescriptorAllocator 定义
DescriptorAllocator g_DescriptorAllocator[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES] =
{
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
};
看的到这是一个 DescriptorAllocator 类型的数组,这个 g_DescriptorAllocator 数组有 4 个成员,每个成员都是一个
DescriptorAllocator 类型对象,这个类型对堆申请以及句柄申请做了一个池的封装

跟到 Allocate 里看一下
D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate( uint32_t Count )
{
if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count)
{
m_CurrentHeap = RequestNewHeap(m_Type);
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;

if (m_DescriptorSize == 0) m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type); } D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle; m_CurrentHandle.ptr += Count * m_DescriptorSize; m_RemainingFreeHandles -= Count; return ret;

}

没有堆就创建一个堆
跟到 m_CurrentHeap = RequestNewHeap(m_Type);这里
参数 m_Type 就跟 g_DescriptorAllocator[Type]的 Type 一样就是 D3D12_DESCRIPTOR_HEAP_TYPE_RTV

ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(D3D12_DESCRIPTOR_HEAP_TYPE Type)
{
std::lock_guard LockGuard(sm_AllocationMutex);

D3D12_DESCRIPTOR_HEAP_DESC Desc; Desc.Type = Type; Desc.NumDescriptors = sm_NumDescriptorsPerHeap;//256个描述 Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; Desc.NodeMask = 1; Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap; ASSERT_SUCCEEDED(Graphics::g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap))); sm_DescriptorHeapPool.emplace_back(pHeap); return pHeap.Get();

}

函数实现还带锁,那应该是提供给多线程使用的,这里也就是创建了一个堆描述对象,这个堆描述对象的 NumDescriptors 为 256(sm_NumDescriptorsPerHeap = 256)
最后还是通过
g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap);
创建了一个描述堆。
返回了创建的 ID3D12DescriptorHeap 指针
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
这个意思就是取得描述堆起始的句柄
if (m_DescriptorSize == 0)
m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);
这个就是取得每 2 个相邻句柄的指针要偏移多少

D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle; m_CurrentHandle.ptr += Count * m_DescriptorSize; m_RemainingFreeHandles -= Count; return ret;

这个就是说 Count 参数,申请多少个句柄,用 ret 存储返回的句柄,把原句柄按照数量偏移到新位置去。返回 ret(保存着偏移前的位置的句柄)

返回到
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
接着
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);
这句话意思类似:创建一个如 m_pResource(ID3D12Resource 指针)描述的资源对象,并把对象句柄存储在 m_RTVHandle 里,从此 m_RTVHandle 就真正指向了一个 RTV 对象
返回到堆栈上

for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}
循环里每一次运行,就申请了一个 RTVHandler,然后创建了一个 RTV 对象放到这个 RTVHandler 中,这个循环一共运行了 3 次(SWAP_CHAIN_BUFFER_COUNT = 3)

往下
太多代码还未理解。
觉得 dx12 还是需要先把概念扔出来,而不是这里这样那里那样,有了整体的概念,应该好理解很多

这里有一部分描述
https://www.csdn.net/article/a/2016-01-04/15833516

代码里有些类似乎涉及到一些算法,搜索了些资料保存下来慢慢看
双调排序 Bitonic Sort

一些名词
环境光遮蔽

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    9641 引用 • 43836 回帖 • 95 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 183 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    54 引用 • 37 回帖
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖 • 3 关注
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    6 引用 • 15 回帖 • 24 关注
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    168 引用 • 597 回帖
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    170 引用 • 1529 回帖
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖 • 4 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    184 引用 • 1015 回帖 • 1 关注
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 703 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 54 回帖 • 8 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 89 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    143 引用 • 442 回帖 • 1 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    28 引用 • 197 回帖 • 33 关注
  • 导航

    各种网址链接、内容导航。

    43 引用 • 177 回帖
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    173 引用 • 3849 回帖
  • ActiveMQ

    ActiveMQ 是 Apache 旗下的一款开源消息总线系统,它完整实现了 JMS 规范,是一个企业级的消息中间件。

    19 引用 • 13 回帖 • 678 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 649 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 812 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 548 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 692 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 678 关注
  • Follow
    4 引用 • 12 回帖 • 11 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    57 引用 • 25 回帖