This repository has been archived on 2024-12-25. You can view files and clone it, but cannot push or open issues or pull requests.
OldPascalProjects/OpenGL и OpenCL/Справка OpenCLABC.html
2024-03-10 20:32:51 +03:00

2087 lines
95 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<html>
<head>
<meta charset="utf-8">
<style>
/* ============================== *\
page base
\* ============================== */
body {
background-color: #F0F0F0;
margin: 0px;
overflow: hidden;
}
.page-base {
position: absolute;
}
#splitter {
width: 5px;
cursor: col-resize;
box-shadow: 0 0 4px #B0B0B0;
}
.page-container {
margin: 7.5px;
background-color: white;
overflow: auto;
}
#page-select-container {
white-space: nowrap;
}
#page-display-container-body {
padding: 7px;
width: fit-content;
}
/* ============================== *\
page select
\* ============================== */
.arrow-page-root {
cursor: pointer;
}
.dot-page-root {
cursor: default;
margin-left: 5px;
margin-right: 6px;
}
.ps-root-container {
margin: 7px;
}
.ps-folder {
display: table;
}
.ps-folder-name {
cursor: pointer;
padding: 1;
}
.ps-container {
margin-left: 7;
padding-left: 7;
border-left: thin solid;
border-color: #80808080;
}
.ps-page {
}
.ps-page-name {
cursor: pointer;
padding: 1;
}
/* ============================== *\
page display
\* ============================== */
blockquote > *:first-child {
margin-top: 0;
}
blockquote > *:last-child {
margin-bottom: 0;
}
blockquote {
margin-left: 0;
margin-right: 0;
padding: 12px;
border-left: 5px solid #d0d0d0;
background-color: #f7f7f7;
}
.smart-link {
color: blue;
cursor: pointer;
}
.spoiler {
margin-left: 8px;
}
.spoiler-wrap {
border: solid;
border-width: 0px;
padding-left: 2px;
}
.spoiler-name {
border: solid;
border-width: 0px;
}
.code-block {
display: inline-block;
cursor: text;
border-radius: 3px;
padding: 4px;
padding-right: 12px;
margin: 0;
position: relative;
}
.code-block-copier {
cursor: pointer;
padding: 2px;
position: absolute;
top: 0;
border: thin solid;
border-radius: 3.7px 0px 3.7px 3.7px;
box-shadow: -2px 2px 3px #0000004a;
background: linear-gradient(0deg, #f0f0f0, white);
}
.inline-code, .code-block {
background: #F0F0F0;
}
blockquote .inline-code,
blockquote .code-block {
background: #DEDEDE;
}
.code-keyword {
font-weight: bold;
}
.code-build-in {
color: blue;
}
.code-red {
color: red;
font-weight: bold;
}
.code-glowing-bracket {
color: red;
background-color: #C0C0C0;
}
</style>
</head>
<body>
<div class="page-base" id="page-select">
<div class="page-container" id="page-select-container"></div>
</div>
<div class="page-base" id="page-display">
<div class="page-container" id="page-display-container">
<div id="page-display-container-body"></div>
</div>
</div>
<div class="page-base" id="splitter"></div>
<script>
/* ============================== *\
misc
\* ============================== */
const selected_name_color = "#eaeaea";
const pointed_name_color = "#d1faff";
var localStorageKey = "";
const get_style = (el)=>el.currentStyle || window.getComputedStyle(el);
var spl_resize_event = [];
/* ============================== *\
page load
\* ============================== */
var root_folder = null;
var currently_loading_folder = null;
var prev_opened_folders = null;
var opened_folders = [];
var page_by_path = {};
const define_page_with_path = (path, page)=>{
if (page_by_path[path])
console.error(`page with path "${path}" defined multiple times`); else
page_by_path[path] = page;
if (broken_links[path])
{
for (let lnk of broken_links[path])
lnk.fix(page);
delete broken_links[path];
}
}
const on_start_folder = (folder_name, root_page)=>{
let res = {
folders: [],
pages: [],
content_ref: root_page,
container: document.createElement("div"),
name: folder_name,
};
if (root_page)
{
root_page.tree_obj = res;
document.getElementById("page-display-container-body").append(root_page);
}
if (!root_folder)
{
root_folder = res;
res.path = "";
localStorageKey = `spec : ${folder_name} : `;
let prev_opened_folders_str = localStorage[localStorageKey+"opened_folders"];
prev_opened_folders = prev_opened_folders_str ? JSON.parse(prev_opened_folders_str) : [];
res.container.className = "ps-root-container";
document.getElementById("page-select-container").append(res.container);
if (root_page) select_page(root_page);
} else
{
currently_loading_folder.folders.push(res);
res.root = currently_loading_folder;
res.path = res.root.path + folder_name + '/';
res.body = document.createElement("div");
res.body.className = "ps-folder";
{
let ind = prev_opened_folders.indexOf(res.path);
res.state = ind!==-1;
if (res.state)
{
prev_opened_folders.splice(ind,1);
opened_folders.push(res.path);
}
}
currently_loading_folder.container.append(res.body);
res.update = ()=>{
res.state_span.innerHTML = String.fromCharCode( res.empty? 0x2022 : res.state ? 0x25BC : 0x25BA );
res.container.hidden = !res.state;
}
res.reverse_state = ()=>{
res.state = !res.state;
if (res.state)
opened_folders.push(res.path); else
opened_folders.splice(opened_folders.indexOf(res.path), 1);
localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
res.update();
};
res.state_span = document.createElement("span");
res.state_span.className = "dot-page-root";
res.state_span.innerHTML = String.fromCharCode( 0x2022 );
res.body.append(res.state_span);
res.name_span = document.createElement("span");
res.name_span.className = "ps-folder-name";
res.name_span.innerHTML = folder_name;
if (root_page) root_page.name_span = res.name_span;
res.body.append(res.name_span);
//
if (root_page) res.name_span.addEventListener("click", ()=>select_page(root_page));
res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
res.container.className = "ps-container";
res.body.append(res.container);
}
currently_loading_folder = res;
if (root_page) fix_links(root_page);
}
const on_page_added = (page)=>{
document.getElementById("page-display-container-body").append(page);
let res = {
root: currently_loading_folder,
name: page.getAttribute("page_name"),
};
res.path = currently_loading_folder.path + res.name;
res.cont_div = document.createElement("div");
res.cont_div.className = "ps-page";
currently_loading_folder.container.append(res.cont_div);
res.dot_span = document.createElement("span");
res.dot_span.className = "dot-page-root";
res.dot_span.innerHTML = String.fromCharCode( 0x2022 );
res.cont_div.append(res.dot_span);
res.name_span = document.createElement("span");
res.name_span.innerHTML = res.name + "<br>";
res.name_span.className = "ps-page-name";
res.name_span.addEventListener("click", ()=>select_page(page));
res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
res.cont_div.append(res.name_span);
page.name_span = res.name_span;
res.content_ref = page;
page.tree_obj = res;
currently_loading_folder.pages.push(res);
define_page_with_path(res.path, page);
fix_links(page);
if (!selected_page && !document.location.hash) select_page(page);
}
const on_end_folder = ()=>{
if (currently_loading_folder!=root_folder) {
let folder = currently_loading_folder;
if ( folder.folders.length || folder.pages.length )
{
folder.state_span.className = "arrow-page-root";
folder.state_span.addEventListener("click", folder.reverse_state);
folder.name_span.addEventListener("dblclick", folder.reverse_state);
currently_loading_folder.update();
} else
folder.empty = true;
}
if (currently_loading_folder !== root_folder)
{
if (!currently_loading_folder.content_ref)
{
let res = document.createElement("div");
res.hidden = true;
res.name_span = currently_loading_folder.name_span;
res.name_span.addEventListener("click", ()=>select_page(res));
document.getElementById("page-display-container-body").append(res);
currently_loading_folder.content_ref = res;
res.tree_obj = currently_loading_folder;
if (currently_loading_folder.folders.length)
{
let h = document.createElement("h1");
h.innerHTML = "Под-папки:";
res.append(h);
let l = document.createElement("ul");
for (let folder of currently_loading_folder.folders)
{
let li = document.createElement("li");
li.innerHTML = folder.name_span.innerHTML;
make_smart_link(li, folder.content_ref);
l.append(li);
}
res.append(l);
}
if (currently_loading_folder.pages.length)
{
let h = document.createElement("h1");
h.innerHTML = "Страницы:";
res.append(h);
let l = document.createElement("ul");
for (let page of currently_loading_folder.pages)
{
let li = document.createElement("li");
li.innerHTML = page.name_span.innerHTML;
make_smart_link(li, page.content_ref);
l.append(li);
}
res.append(l);
}
}
define_page_with_path(currently_loading_folder.path, currently_loading_folder.content_ref);
currently_loading_folder = currently_loading_folder.root;
}
}
/* ============================== *\
page display
\* ============================== */
var selected_page = null;
var scroll_by_page = {};
const is_selected = (name_span)=>(!selected_page || selected_page.name_span!==name_span);
const select_page = (new_page)=>{
if (selected_page == new_page) return;
document.location.hash = new_page.tree_obj.path;
fix_element(new_page);
let container = document.getElementById("page-display-container");
if (selected_page)
{
scroll_by_page[selected_page.tree_obj.path] = container.scrollTop;
selected_page.hidden = true;
let name_span = selected_page.name_span;
if (name_span) name_span.style.backgroundColor = "inherit";
}
selected_page = new_page;
if (selected_page)
{
selected_page.hidden = false;
let name_span = selected_page.name_span;
if (name_span) name_span.style.backgroundColor = selected_name_color;
let tree_obj = new_page.tree_obj.root;
while (tree_obj)
{
if ( tree_obj.state==false && tree_obj.reverse_state ) tree_obj.reverse_state();
tree_obj = tree_obj.root;
}
container.scrollTop = scroll_by_page[selected_page.tree_obj.path] || 0;
}
}
const make_smart_link = (lnk, page)=>{
lnk.className = "smart-link";
lnk.addEventListener("click", ()=>select_page(page));
}
var broken_links = {};
const define_broken_lnk = (path, source, fixer)=>{
if (!broken_links[path]) broken_links[path] = [];
broken_links[path].push({
source: source,
fix: fixer,
});
}
const fix_links = (page)=>{
for (let lnk of page.getElementsByTagName('a'))
{
lnk.path = lnk.getAttribute("path");
if (!lnk.path) continue;
lnk.removeAttribute("path");
start_folder = currently_loading_folder;
while (lnk.path.startsWith("../"))
{
start_folder = start_folder.root;
lnk.path = lnk.path.substr(3);
}
lnk.path = start_folder.path+lnk.path;
let lnk_page = page_by_path[lnk.path];
if (lnk_page)
make_smart_link(lnk, lnk_page); else
define_broken_lnk(lnk.path, page.tree_obj.path, new_page=>make_smart_link(lnk, new_page))
}
}
const code_words_color = {
"pas": {
"keyword": [
"begin","end", "var", "uses", "as", "new", "try", "except", "on", "do", "const",
"procedure", "function", "lock", "type", "class","record", "sizeof","typeof", "external",
"static", "array", "where", "or","and", "public","private", "property","constructor", "default",
"of", "if","then","else", "div","mod", "extensionmethod", "while",
],
"build-in": [
"nil", "self", "true", "false",
"string","char", "object", "pointer", "single","real",
"shortint", "smallint", "integer", "int64",
"byte", "word", "longword", "cardinal", "uint64",
],
"red": ["ToDo"],
},
"cl-c": {
"keyword": ["__kernel", "__global"],
"build-in": ["int", "void"],
},
"default": {
}
}
const fix_element = (page)=>{
if (page.fixed) return;
page.fixed = true;
for (let spoiler of page.getElementsByClassName('spoiler'))
{
let get_spoiler_text = ()=> + ' ' + spoiler.getAttribute('summary');
let wrap = document.createElement('p');
wrap.className = "spoiler-wrap";
wrap.update = ()=>{
wrap.state_span.innerHTML = String.fromCharCode( spoiler.hidden ? 0x25BA : 0x25BC );
wrap.style.borderLeftWidth = spoiler.hidden ? 0 : 1;
wrap.name_span.style.borderBottomWidth = spoiler.hidden ? 1 : 0;
wrap.style.marginLeft = spoiler.hidden ? 1 : 0;
}
wrap.state_span = document.createElement("span");
wrap.append(wrap.state_span);
wrap.name_span = document.createElement("span");
wrap.name_span.className = "spoiler-name";
wrap.name_span.innerHTML = spoiler.getAttribute("summary");
wrap.append(wrap.name_span);
wrap.update();
wrap.reverse_state = ()=>{
spoiler.hidden = !spoiler.hidden;
wrap.update();
}
wrap.state_span.addEventListener("click", wrap.reverse_state);
wrap.name_span.addEventListener("click", wrap.reverse_state);
wrap.state_span.style.cursor = "pointer";
wrap.name_span.style.cursor = "pointer";
spoiler.replaceWith(wrap);
wrap.append(spoiler);
}
for (let code of page.getElementsByTagName('code'))
{
let w_to_regex = (w)=>`(?<!\\w)${w}(?!\\w)`;
let code_html = code.innerHTML;
// Автоопределение языка кода
if (!code.className)
{
let best = {lang: null, c: 0};
let multiple_best = true;
for (let lang_name in code_words_color)
{
let c = 0;
for (let wordt in code_words_color[lang_name])
for (let w of code_words_color[lang_name][wordt])
{
var m = code_html.match(new RegExp( w_to_regex(w), "gi" ));
if (m) c += m.length;
}
if (best.c == c)
multiple_best = true; else
if (best.c < c)
{
multiple_best = false;
best.lang = lang_name;
best.c = c;
}
}
if (multiple_best)
code.className = "language-default"; else
code.className = "language-" + best.lang;
}
// Подсветка особых слов в коде
{
let lang = code.className.substr("language-".length);
let curr_cw = code_words_color[lang];
if (!curr_cw) curr_cw = code_words_color["default"];
for (let wordt in curr_cw)
code_html = code_html.replace(
new RegExp(curr_cw[wordt].map(w_to_regex).join('|'),"gi"),
w=> `<span class="code-${wordt}">${w}</span>`
);
}
// Выделение скобок
{
var br_types = {
op: ["(", "[", "{", "&lt;", "'"],
cl: [")", "]", "}", "&gt;", "'"],
}
for (let op in br_types)
for (let i=0; i<br_types[op].length; i++)
code_html = code_html.replace(
new RegExp('\\'+br_types[op][i], "g"),
`<span class=bracket ${ op=="op" ? "op=true" : "" } bt=${i}>${br_types[op][i]}</span>`
);
}
// Выделение первой строки
if (code.parentElement.tagName == "PRE")
{
let ind = code_html.indexOf('\n');
if (ind != code_html.length-1)
code_html = `<span class="code-first-line">${code_html.slice(0,ind)}</span>${code_html.slice(ind)}`;
}
code.innerHTML = code_html;
code.firstLine = code.children[0];
if (code.firstLine && code.firstLine.className != "code-first-line") delete code.firstLine;
// Подсветка скобок
{
let br_st = [];
for (let obj2 of code.getElementsByClassName("bracket"))
{
let b2t = obj2.getAttribute("bt");
if (obj2.getAttribute("op"))
br_st.push({
obj: obj2,
bt: b2t,
}); else
{
let b1 = br_st.pop();
let b0 = null;
if (!b1) continue;
if (b1.obj == obj2.parentElement)
{
b0 = b1;
b1 = br_st.pop();
}
if (!b1) continue;
if (b1.bt == b2t)
{
let obj1 = b1.obj;
let on_enter = ()=>{
obj1.className = "code-glowing-bracket";
obj2.className = "code-glowing-bracket";
}
let on_leave = ()=>{
obj1.className = null;
obj2.className = null;
}
obj1.addEventListener("mouseenter", on_enter);
obj2.addEventListener("mouseenter", on_enter);
obj1.addEventListener("mouseleave", on_leave);
obj2.addEventListener("mouseleave", on_leave);
} else
{
br_st.push(b1);
if (b0) br_st.push(b0);
}
}
}
}
if (code.parentElement.tagName == "PRE")
{
let pre = code.parentElement;
pre.className = 'code-block';
let wrap = document.createElement('p');
pre.replaceWith(wrap);
wrap.append(pre);
} else
code.className = "inline-code";
}
page.style.visibility = "hidden";
page.hidden = false;
for (let code of page.getElementsByTagName('code'))
{
if (code.firstLine)
{
let pre = code.parentElement;
let copy_button = document.createElement('div');
copy_button.className = "code-block-copier";
copy_button.innerText = "Копировать";
copy_button.style.visibility = "hidden";
pre.append(copy_button);
copy_button.addEventListener("click", ()=>{
if (document.selection) { // IE
var range = document.body.createTextRange();
range.moveToElementText(code);
range.select();
} else if (window.getSelection) { // other browsers
var range = document.createRange();
range.selectNode(code);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
var text = code.innerText;
navigator.clipboard.writeText(text.substring(0,text.length-1));
});
copy_button.reset_right = ()=>{
pre_st = get_style(pre);
let container = document.getElementById("page-display-container");
let x = pre.getBoundingClientRect().right - (container.getBoundingClientRect().left+container.clientWidth);
if (x<0) x = 0; else
{
let max_x = pre.offsetWidth - copy_button.offsetWidth;
if (x>max_x) x = max_x;
}
copy_button.style.right = x;
};
let try_reset_right = ()=>{
if (!copy_button.hidden)
copy_button.reset_right();
};
spl_resize_event.push(try_reset_right);
window.addEventListener("resize", try_reset_right);
pre.addEventListener("mouseenter", ()=>{
copy_button.hidden = false;
copy_button.reset_right();
});
pre.addEventListener("mouseleave", ()=>copy_button.hidden = true);
copy_button.addEventListener("mousedown", ()=>copy_button.style.background = "linear-gradient(180deg, rgb(240, 240, 240), white)");
copy_button.addEventListener("mouseup", ()=>copy_button.style.background = "linear-gradient( 0deg, rgb(240, 240, 240), white)");
copy_button.addEventListener("mouseleave", ()=>copy_button.style.background = "linear-gradient( 0deg, rgb(240, 240, 240), white)");
pre.style.minWidth =
code.firstLine.offsetWidth +
copy_button.offsetWidth +
0
;
copy_button.hidden = true;
copy_button.style.visibility = null;
}
}
page.hidden = true;
page.style.visibility = null;
}
/* ============================== *\
init
\* ============================== */
{
if (document.location.hash)
{
let get_hash = ()=>decodeURIComponent(document.location.hash.substr(1));
let define_hash_lnk = ()=>define_broken_lnk(
get_hash(),
"document.location.hash",
page=>select_page(page)
);
define_hash_lnk();
window.addEventListener("hashchange", ()=>{
let hash_page = page_by_path[get_hash()];
if (hash_page)
select_page(hash_page); else
define_hash_lnk();
});
}
let page_select = document.getElementById("page-select");
let page_display = document.getElementById("page-display");
let splitter = document.getElementById("splitter");
let ww = window.innerWidth;
let wh = window.innerHeight;
for (let cont of document.getElementsByClassName("page-container"))
{
let par = cont.parentElement;
par.update_cont = (w)=>{
cont.style.width = w-15;
cont.style.height = wh-15;
};
}
let spl_X = ww * 0.30;
const reset_spl = ()=>{
ww = window.innerWidth;
wh = window.innerHeight;
page_select.style.height = wh + "px";
splitter.style.height = wh + "px";
page_display.style.height = wh + "px";
if (ww-5<spl_X) spl_X = ww-5;
if (spl_X<5) spl_X = 5;
page_select.style.width = spl_X + "px";
splitter.style.left = spl_X + "px";
let x2 = spl_X+splitter.clientWidth;
let w2 = ww - x2;
page_display.style.left = x2 + "px";
page_display.style.width = w2 + "px";
page_select.update_cont(spl_X);
page_display.update_cont(w2);
for (let handler of spl_resize_event)
handler();
}
reset_spl();
window.addEventListener("resize", ()=>reset_spl());
splitter.addEventListener("dblclick", ()=>{
let w = 0;
let psc = document.getElementsByClassName("page-container")[0];
let psc2 = psc.children[0];
for (let n of psc2.children)
if (n.clientWidth>w) w = n.clientWidth;
let get_margin = (el)=>{
let style = get_style(el);
return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
}
spl_X = w +
get_margin(psc) +
get_margin(psc2)
;
reset_spl();
});
let spl_grabed = false;
splitter.addEventListener("mousedown", (e)=>{
spl_grabed=true;
e.preventDefault();
});
window.addEventListener("mousemove", (e)=>{if (spl_grabed) {
spl_X = e.clientX - splitter.clientWidth/2;
reset_spl();
e.preventDefault();
}});
window.addEventListener("mouseup", ()=>spl_grabed=false);
}
window.onload = ()=>{
for (path in broken_links)
console.error(`Page "${path}" referenced ${broken_links[path].length} times but not found:`, broken_links[path].map(lnk=>lnk.source));
localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
if (prev_opened_folders.length) console.log("folders were marked as opened, but not found:", prev_opened_folders);
delete prev_opened_folders;
}
</script>
<script>on_start_folder("CL ABC", null)</script>
<script>on_start_folder("Общие сведения", null)</script>
<div id="page-1" page_name="О справке" hidden=true>
<p>Данная справка относится к модулю <code>OpenCLABC</code>, входящему в состав стандартных модулей языка <code>PascalABC.NET</code>.</p>
<p>Модуль <code>OpenCLABC</code> это высокоуровневая оболочка модуля <code>OpenCL</code>.<br />
Это значит, что с <code>OpenCLABC</code> можно писать гораздо меньше кода в больших и сложных программах,
однако такой же уровень микроконтроля как с модулем <code>OpenCL</code> недоступен.
Например, напрямую управлять <code>cl_event</code>'ами в <code>OpenCLABC</code> невозможно.
Вместо этого надо использовать операции с очередями (например, сложение и умножение очередей)</p>
<p>Справка модуля <code>OpenCL</code> отсутствует. Вместо неё смотрите:</p>
<ul>
<li><a href="%D0%93%D0%B0%D0%B9%D0%B4%20%D0%BF%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E%20OpenGL%20%D0%B8%20OpenCL.html">Общий гайд по использованию модулей <code>OpenGL</code> и <code>OpenCL</code></a></li>
<li><a href="https://www.khronos.org/registry/OpenCL/">Контейнер справок библиотеки <code>OpenCL</code>, на которой основан модуль <code>OpenCL</code></a></li>
</ul>
<p>Если в справке или модуле найдена ошибка, или чего-либо не хватает - пишите в <a href="https://github.com/SunSerega/POCGL/issues">issue</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-1"))</script>
<div id="page-2" page_name="Термины, которые часто путают новички" hidden=true>
<ul>
<li><p>CPU — Центральное Процессорное Устройство (процессор);</p>
</li>
<li><p>GPU — Графическое Процессорное Устройство (видеокарта);</p>
</li>
<li><p>Команда — запрос на выполнение чего-либо. К примеру:</p>
<ul>
<li>Запрос на запуск программы на GPU;</li>
<li>Запрос на начало чтения данных из памяти GPU в оперативную память;</li>
</ul>
<p><strong>Называть процедуры и функции командами ошибочно!</strong></p>
</li>
<li><p>Подпрограмма — процедура или функция;</p>
</li>
<li><p>Метод — особая подпрограмма, вызываемая через экземпляр:</p>
<ul>
<li>К примеру, метод <code>Context.SyncInvoke</code> выглядит в коде как <code>cont.SyncInvoke(...)</code>, где <code>cont</code> — переменная типа <code>Context</code>;</li>
</ul>
</li>
</ul>
<p>Остальные непонятные термины можно найти в справке <code>PascalABC.NET</code> или в интернете.</p>
</div>
<script>on_page_added(document.getElementById("page-2"))</script>
<script>on_end_folder()</script>
<div id="page-3" page_name="" hidden=true>
<p>Сам модуль <code>OpenCLABC</code> - оболочка модуля <code>OpenCL</code>. Это значит, что внутри он использует
содержимое <code>OpenCL</code>, но при подключении - показывает только свой личный функционал.</p>
<p>Так же множество типов из <code>OpenCLABC</code> являются оболочками типов из <code>OpenCL</code>.</p>
<p>Тип <code>CommandQueue</code> использует тип <code>cl_command_queue</code>, но предоставляет очень много не связанного с <code>cl_command_queue</code> функционала.</p>
<p>&quot;Простые&quot; типы-оболочки модуля <code>OpenCLABC</code> предоставляют только функционал соответствующего
типа из модуля <code>OpenCL</code>... в более презентабельном виде. Из общего - у таких типов есть:</p>
<ul>
<li><p>Свойство <code>.Native</code>, возвращающее внутренний объект из модуля <code>OpenCL</code>.<br />
Если вам не пришлось, по какой-либо причине, использовать <code>OpenCLABC</code> и <code>OpenCL</code> вместе - это свойство может понадобится только для дебага.</p>
</li>
<li><p>Свойство <code>.Properties</code>, возвращающее объект свойств внутреннего объекта.<br />
Свойства неуправляемого объекта никак не обрабатываются и не имеют описаний.
Но типы этих свойств всё равно преобразуются в управляемые (особо заметно на строках и массивах).</p>
</li>
</ul>
<hr />
<h1>Список простых типов-оболочек:</h1>
<ul>
<li><a path="Platform/"> Platform </a> - Платформа, объединяющая несколько совместимых устройств;</li>
<li><a path="Device/"> Device </a> - Устройство, поддерживающее OpenCL;</li>
<li><a path="Context/"> Context </a> - Контекст выполнения;</li>
<li><a path="Buffer/"> Buffer </a> - Область памяти GPU;</li>
<li><a path="Kernel/"> Kernel </a> - Подпрограмма, выполняемая на GPU;</li>
<li><a path="ProgramCode/"> ProgramCode </a> - Контейнер kernel'ов.</li>
</ul>
</div>
<script>on_start_folder("Простые оболочки", document.getElementById("page-3"))</script>
<div id="page-4" page_name="" hidden=true>
<p>В списке устройств контекста могут быть только совместимые друг с другом устройства.<br />
Коллекция совместимых устройств называется платформой и хранится в объектах типа <code>Platform</code>.</p>
<hr />
<p>Обычно платформы получают из статического свойства <code>Platform.All</code>:</p>
<pre><code>uses OpenCLABC;
begin
var pls := Platform.All;
pls.PrintLines;
end.
</code></pre>
</div>
<script>on_start_folder("Platform", document.getElementById("page-4"))</script>
<script>on_end_folder()</script>
<div id="page-5" page_name="" hidden=true>
<p>Обычно устройства получают статическим методом <code>Device.GetAllFor</code>:</p>
<pre><code>uses OpenCLABC;
begin
foreach var pl in Platform.All do
begin
Writeln(pl);
var dvcs := Device.GetAllFor(pl, DeviceType.DEVICE_TYPE_ALL);
if dvcs&lt;&gt;nil then dvcs.PrintLines;
Writeln('='*30);
end;
end.
</code></pre>
<p>И в большинстве случаев - это всё что вам понадобится.</p>
<hr />
<p>Но если где то нужен более тонкий контроль - можно создать несколько виртуальных
под-устройств, каждому из которых даётся часть ядер изначального устройства.<br />
Для этого используются методы <code>.Split*</code>:</p>
<pre><code>uses OpenCLABC;
begin
var dvc := Context.Default.MainDevice;
Writeln('Поддерживаемые типы .Spilt-ов:');
// Пустой список, или DEVICE_PARTITION_BY_COUNTS_LIST_END, если ни 1 не поддерживается
dvc.Properties.PartitionProperties.PrintLines;
Writeln('='*30);
Writeln('Виртуальные устройства, по 1 ядру каждое:');
// Если упадёт потому что слишком много
// устройств - пожалуйста, напишите в issue
dvc.SplitEqually(1).PrintLines;
Writeln('='*30);
Writeln('Два устройства, 1 и 2 ядра соответственно:');
dvc.SplitByCounts(1,2).PrintLines;
end.
</code></pre>
</div>
<script>on_start_folder("Device", document.getElementById("page-5"))</script>
<script>on_end_folder()</script>
<div id="page-6" page_name="" hidden=true>
<p>Для отправки команд в GPU необходим контекст (объект типа <code>Context</code>):<br />
Он содержит информацию о том, какие устройства будут использоваться для выполнения кода на GPU и хранения содержимого буферов.</p>
<hr />
<p>Создать контекст можно конструктором (<code>new Context(...)</code>).<br />
Контекст можно и не создавать, используя везде свойство <code>Context.Default</code>.<br />
Неявные очереди всегда используют <code>Context.Default</code>.</p>
<p>Изначально этому свойству присваивается контекст, использующий GPU, если оно имеется,
или любое другое устройство, поддерживающее OpenCL, если GPU отсутствует.</p>
<p>Если устройств поддерживающих <code>OpenCL</code> нет - <code>Context.Default</code> будет <code>nil</code>.<br />
Однако такая ситуация на практике невозможна, потому что OpenCL поддерживается
практически всеми современными устройствами, занимающимися выводом изображения на экран.<br />
Если <code>Context.Default = nil</code> - переустановите графические драйверы.</p>
<hr />
<p><code>Context.Default</code> можно перезаписывать.<br />
Используйте эту возможность только если во всей программе нужен общий контекст, но не стандартный.</p>
<p>Если ваша программа достаточно сложная чтобы нуждаться в нескольких контекстах - лучше не использовать
<code>Context.Default</code>. И присвоить ему <code>nil</code>, чтобы не использовать случайно (к примеру, неявной очередью).</p>
</div>
<script>on_start_folder("Context", document.getElementById("page-6"))</script>
<script>on_end_folder()</script>
<div id="page-7" page_name="" hidden=true>
<p>Программам, выпоняемые на GPU, не удобно использовать оперативную память.
Поэтому обычно надо выделять память на самом GPU.<br />
Объекты типа <code>Buffer</code> представляют такую область памяти.</p>
<hr />
<p>Буфер создаётся конструктором (<code>new Buffer(...)</code>).</p>
<p>Но если не передавать контекст в конструктор - память на GPU выделится только при вызове метода <code>Buffer.Init</code>.<br />
Если вызвать <code>Buffer.Init</code> 2 раза - память освободится и выделится заново.</p>
<p>Когда на GPU выделяется память - она <strong>НЕ</strong> очищается нулями, а значит содержит мусорные данные.</p>
<p>Если метод <code>Buffer.Init</code> не был вызван до первой операции чтения/записи - он будет вызван автоматически.
В таком случае в качестве контекста в котором выделяется память берётся тот,
на котором вызвали <code>.BeginInvoke</code>, который запустил команду чтения/записи буфера.</p>
<p>Память на GPU можно моментально освободить, вызвав метод <code>Buffer.Dispose</code>.
Но если снова использовать буфер, у которого освободили память - память выделится заново.</p>
<p>Если сборщик мусора удаляет объект типа <code>Buffer</code> - <code>.Dispose</code> вызывается автоматически.</p>
<hr />
<p>Но кроме выделения памяти GPU - OpenCL так же позволяет выделять память внутри другого буфера.<br />
Для этого используется тип <code>SubBuffer</code>:</p>
<pre><code>uses OpenCLABC;
begin
var c := Context.Default;
// Не обязательно MainDevice, можно взять любое устройство из контекста
var align := c.MainDevice.Properties.MemBaseAddrAlign;
var b := new Buffer(align*2, c);
// size может быть любым, но origin
// должно быть align*N, где N - целое
var b1 := new SubBuffer(b, 0, Min(123,align));
var b2 := new SubBuffer(b, align, align);
Writeln(b1);
Writeln(b2);
end.
</code></pre>
</div>
<script>on_start_folder("Buffer", document.getElementById("page-7"))</script>
<script>on_end_folder()</script>
<div id="page-8" page_name="" hidden=true>
<p>Объект типа <code>Kernel</code> представляет одну подпрограмму в OpenCL-C коде,
объявленную с ключевым словом <code>__kernel</code>.</p>
<hr />
<p>Обычно <code>Kernel</code> создаётся через индексное свойтсво <code>ProgramCode</code>:</p>
<pre><code>var code: ProgramCode;
...
var k := code['KernelName'];
</code></pre>
<p>Тут <code>'KernelName'</code> — имя подпрограммы-kernel'а в исходном коде (регистр важен!).</p>
<hr />
<p>Так же можно получить список всех kernel'ов объекта <code>ProgramCode</code>, методом <code>ProgramCode.GetAllKernels</code>:</p>
<pre><code>var code: ProgramCode;
...
var ks := code.GetAllKernels;
ks.PrintLines;
</code></pre>
<hr />
<p>Методы, запускающие <code>Kernel</code> принимают специальные аргументы типа <code>KernelArg</code>, которые передаются в OpenCL-C код.<br />
Экземпляр <code>KernelArg</code> может быть создан из нескольких типов значений, а точнее:</p>
<pre><code>uses OpenCLABC;
begin
var k: Kernel;
var val1 := 3;
var val2 := 5;
k.Exec1(1,
// Передавать можно:
// Буфер
new Buffer(1),
// Очередь возвращающую буфер
HFQ(()-&gt;new Buffer(1)),
// В том числе BufferCommandQueue
Buffer.Create(1).NewQueue,
// Размерное значение
val1,
HFQ(()-&gt;val1),
// И указатель на размерное значение
// (в kernel попадёт само значение, не указатель)
@val2,
// Так нельзя, потому что val1 уже захвачена лямбдой из HFQ
// @val1,
// Расширенный набор параметров
KernelArg.FromPtr(new System.IntPtr(@val2), new System.UIntPtr(sizeof(integer)))
);
end.
</code></pre>
<p>Обратите внимание, <code>KernelArg</code> из указателя на <code>val2</code> будет немного эффективнее
чем <code>KernelArg</code> из самого значения <code>val2</code>. Но эту возможность стоит использовать
только как тонкую оптимизацию, потому что много чего может пойти не так.
Если передавать <code>@val2</code> в качестве <code>KernelArg</code> - надо знать все тонкости.</p>
<hr />
<p>Если <code>@val2</code> передали вкачестве <code>KernelArg</code>:</p>
<ol>
<li><p><code>val2</code> не может быть глобальной переменной или полем класса:</p>
<pre><code>uses OpenCLABC;
type
t1 = class
val1 := 1; // Не подходит, потому что поле класса
static val2 := 2; // И статичных полей это тоже касается
end;
var
val3 := 3; // Глобальные переменные - тоже статические поля
k: Kernel; // k не важно где объявлена
procedure p1;
// Теоретически подходит, но вообще это плохой стиль кода
var val4 := 4;
begin
// А val5 однозначно подходит, потому что объявлена не только в той же
// подпрограмме, но и прямо перед использованием в k.Exec*
var val5 := 5;
k.Exec1(1,
// Это единственные 2 переменные, которые можно передавать адресом
@val4,
@val5
);
end;
begin end.
</code></pre>
</li>
<li><p><code>val2</code> не должно быть захвачего лямбдой.<br />
Хотя указатель на <code>val2</code> уже можно захватывать:</p>
<pre><code>uses OpenCLABC;
begin
var k: Kernel;
var val1 := 3;
var val2 := 5;
// На val1 всё ещё накладываются все ограничени,
// когда val1_ptr использована в качестве KernelArg
// Но к самой val1_ptr эти ограничения не применяются
var val1_ptr := @val1;
k.Exec1(1,
val1,
HFQ(()-&gt;val1_ptr^), // захватили val1_ptr, а не val1
// val1 нигде не захвачена, поэтому теперь так можно
@val1,
val1_ptr // то же самое
);
end.
</code></pre>
</li>
<li><p>Выходить из подпрограммы, где объявили <code>val2</code> нельзя, пока <code>.Exec</code> не закончит выполнятся.
Это так же значит, что возвращать очередь, содержащую <code>KernelArg</code> из <code>@val2</code> обычно нельзя.<br />
Но это можно обойти, если объявлять переменную в другой подпрограмме, а дальше передавать только её адрес:</p>
<pre><code>uses OpenCLABC;
var k: Kernel; // Вообще лучше передавать параметром в p2
// Обратите внимание - val принимает var-параметром
// То есть, p2 принимает адрес val
// (можно так же принимать указатель, но это не так удобно)
// Иначе переменная будет копироваться при передаче
// То есть без var перед параметром - val тут
// будет новой переменной, объявленной в p2 а не в p1
function p2(var val: integer): CommandQueueBase;
begin
Result := k.NewQueue.AddExec1(1,
@val
);
end;
procedure p1;
begin
var val: integer;
var q := p2(val);
// Опять же, q не должна продолжать выпоняться
// после выхода из p1, потому что тут объявлена val
Context.Default.SyncInvoke(q);
end;
begin
p1;
end.
</code></pre>
</li>
</ol>
<p>Компилятор не заставит вас следовать этим ограничениям. И программа может даже работать, игнорируя большинство сказанного тут.<br />
Но потом вы добавите что то в совсем другой части программы, или запустите её на
другом компьютере и она вдруг начнёт выводить мусорные значения, или падать с ошибками
вроде <code>AccessViolationException</code>, которые совершенно не объясняют в чём проблема.</p>
<p>Если очень интересно откуда берутся эти ограничения - начните с применения .Net декомпилятора к .exe файлам,
получаемым при компиляции программ с данной страницы. Что останется непонятно - можете спрашивать в issue.</p>
</div>
<script>on_start_folder("Kernel", document.getElementById("page-8"))</script>
<script>on_end_folder()</script>
<div id="page-9" page_name="" hidden=true>
<p>Обычные программы невозможно запустить на GPU. Для этого надо писать особые программы.<br />
В контексте OpenCL - эти программы обычно пишутся на языке &quot;OpenCL C&quot; (основанном на языке &quot;C&quot;).</p>
<p>Язык OpenCL-C это часть библиотеки OpenCL, поэтому его справку можно найти <a href="https://www.khronos.org/registry/OpenCL/">там же</a>, где и справку OpenCL.</p>
<p>В <code>OpenCLABC</code> код на языке &quot;OpenCL C&quot; хранится в объектах типа <code>ProgramCode</code>.<br />
Объекты этого типа используются только как контейнеры.
Один объект ProgramCode может содержать любое количествово подпрограмм-kernel'ов.</p>
<hr />
<h1>Есть 2 способа создать объект типа <code>ProgramCode</code>:</h1>
<ul>
<li><p><a path="Создание из исходного кода"> Из исходного кода </a></p>
</li>
<li><p><a path="Создание из бинарного файла"> Из прекомпилированного кода </a></p>
</li>
</ul>
</div>
<script>on_start_folder("ProgramCode", document.getElementById("page-9"))</script>
<div id="page-10" page_name="Создание из исходного кода" hidden=true>
<p>Конструктор <code>ProgramCode</code> (<code>new ProgramCode(...)</code>) принимает тексты исходников программы на языке OpenCL-C.<br />
<strong>Именно тексты исходников, не имена файлов!</strong></p>
<p>Так же, как исходники паскаля хранят в .pas файлах, исходники OpenCL-C кода хранят в .cl файлах.
Но вообще, это не принципиально, потому что код даже не обязательно должен быть в файле.</p>
<p>Так как конструктор <code>ProgramCode</code> принимает текст - исходники программы на OpenCL-C можно хранить даже в
строке в .pas программе. Тем не менее, храненить исходники OpenCL-C кода в .cl файлах обычно удобнее всего.</p>
</div>
<script>on_page_added(document.getElementById("page-10"))</script>
<div id="page-11" page_name="Создание из бинарного файла" hidden=true>
<p>После создания объекта типа <code>ProgramCode</code> из исходников можно вызвать
метод <code>ProgramCode.SerializeTo</code>, чтобы сохранить код в бинарном и прекомпилированном виде.
Обычно это делается отдельной программой (не той же самой, которая будет использовать этот бинарный код).</p>
<p>После этого основная программа может создать объект <code>ProgramCode</code>,
используя статический метод <code>ProgramCode.DeserializeFrom</code>.</p>
<p>Пример можно найти в папке <code>Прекомпиляция ProgramCode</code> или <a href="https://github.com/SunSerega/POCGL/tree/master/Samples/OpenCLABC/%D0%9F%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%86%D0%B8%D1%8F%20ProgramCode">тут</a>.</p>
</div>
<script>on_page_added(document.getElementById("page-11"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
<div id="page-12" page_name="" hidden=true>
<p>Передавать команды для GPU по одной не эффективно.
Гораздо эффективнее передавать несколько команд сразу.</p>
<p>Для этого существуют очереди (типы, наследующие от <code>CommandQueue&lt;T&gt;</code> и <code>CommandQueueBase</code>).
Они хранят произвольное количество команд для GPU.
А при необходимости также и части кода, выполняемые на CPU.</p>
<hr />
<h1>Страницы:</h1>
<ul>
<li><p><a path="Возвращаемое значение очередей/"> Возвращаемое значение </a></p>
</li>
<li><p><a path="Создание очередей/"> Создание </a></p>
</li>
<li><p><a path="Выполнение очередей/"> Выполнение </a></p>
</li>
<li><p><a path="Вложенные очереди/"> Вложенные очереди </a></p>
</li>
</ul>
</div>
<script>on_start_folder("Очередь [команд] (CommandQueue)", document.getElementById("page-12"))</script>
<div id="page-13" page_name="" hidden=true>
<p>У каждого типа-очереди есть свой тип возвращаемого значения.<br />
К примеру, так объявляется переменная в которую можно будет сохранить очередь, возвращающую <code>integer</code>:</p>
<pre><code>var Q1: CommandQueue&lt;integer&gt;;
</code></pre>
<p>Очереди, созданные из буфера или kernel'а возващают свой <code>Buffer</code>/<code>Kernel</code> соответственно, из которого были созданы;<br />
Очереди, созданные с <code>HFQ</code> - значение, которое вернёт переданная функция;<br />
Очереди, созданные с <code>HPQ</code> - значение типа <code>object</code> (и всегда <code>nil</code>).</p>
<p>К примеру:</p>
<pre><code>uses OpenCLABC;
/// Вывод типа и значения объекта
procedure OtpObject(o: object) :=
Writeln( $'{o?.GetType}[{_ObjectToString(o)}]' );
// &quot;o?.GetType&quot; это короткая форма &quot;o=nil ? nil : o.GetType&quot;,
// то есть, берём или тип объекта, или nil если сам объект nil
// _ObjectToString это функция, которую использует Writeln для форматирования значений
begin
var b0 := new Buffer(1);
// Тип - буфер, потому что очередь создали из буфера
OtpObject( Context.Default.SyncInvoke( b0.NewQueue ) );
// Тип - Int32 (то есть integer), потому что это тип по умолчанию для выражения (5)
OtpObject( Context.Default.SyncInvoke( HFQ( ()-&gt;5 ) ) );
// Тип - string, по той же причине
OtpObject( Context.Default.SyncInvoke( HFQ( ()-&gt;'abc' ) ) );
// Тип отсутствует, потому что HPQ возвращает nil
OtpObject( Context.Default.SyncInvoke( HPQ( ()-&gt;Writeln('Выполнилась HPQ') ) ) );
end.
</code></pre>
<p>После выполнения очереди метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br />
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask.WaitRes</code>.</p>
<hr />
<p>Бывает необходимо хранить несколько очередей, с разными возвращаемыми значениями, вместе. К примеру, в переменной типа <code>List&lt;&gt;</code>.<br />
Но в переменной типа <code>CommandQueue&lt;SomeT&gt;</code> можно хранить только очередь с конкретным типом возвращаемого значения <code>SomeT</code>.</p>
<p>Для того, чтобы хранить очереди с разными возвращаемыми значениями в одной переменной - используется <code>CommandQueueBase</code>.<br />
<code>CommandQueueBase</code> это особый тип очереди, у которого не указывается возвращаемое значение.<br />
От него наследует <code>CommandQueue&lt;&gt;</code>, поэтому переменной типа <code>CommandQueueBase</code> можно присвоить любую очередь.</p>
<p>Если попытаться выполнить такую очередь,
или применять операции преобразования очередей, как <code>.ThenConvert</code>,
то тип возвращаемого значения будет восприниматься как <code>Object</code>.</p>
</div>
<script>on_start_folder("Возвращаемое значение очередей", document.getElementById("page-13"))</script>
<script>on_end_folder()</script>
<div id="page-14" page_name="" hidden=true>
<p>Самый простой способ выполнить очередь - вызвать метод <code>Context.SyncInvoke</code>.<br />
Он синхронно выполняет очередь и вызвращает её результат.</p>
<p>Но если надо выполнить очередь асинхронно - лучше использовать метод <code>Context.BeginInvoke</code>.<br />
Он запускает асинхронное выполнение очереди и как только очередь была полностью запущена - возвращает объект типа <code>CLTask&lt;&gt;</code>, у которого есть:</p>
<ul>
<li>Event'ы (но реализованы методами), позволяющие указать что должно выполнятся когда выполнение <code>CLTask</code> завершится.</li>
<li>Свойства, возвращающие оригинальную очередь и контекст выполнения.</li>
<li>Методы для ожидания окончания выполнения и получения результата очереди.</li>
</ul>
<p>Метод <code>Context.SyncInvoke</code> реализован как <code>.BeginInvoke(...).WaitRes</code>.
Поэтому, везде где сказано &quot;... происходит при вызове <code>.BeginInvoke</code>&quot;, это же относится и к <code>.SyncInvoke</code>.</p>
<p>У <code>CLTask</code>, как и у очереди - в <code>&lt;&gt;</code>, указывается тип возвращаемого значения. То есть:</p>
<pre><code>var t: CLTask&lt;integer&gt;;
</code></pre>
<p>В такую переменную можно сохранить только результат <code>Context.BeginInvoke</code> для очереди типа <code>CommandQueue&lt;integer&gt;</code>.</p>
<p>И как и у <code>CommandQueue</code>:</p>
<ul>
<li>Существует так же и тип <code>CLTaskBase</code>, у которого типа возвращаемого значения не указывается.</li>
<li>Переменной типа <code>CLTaskBase</code> можно присвоить <code>CLTask</code> с любым возвращаемым значением.</li>
<li><code>Context.BeginInvoke</code> для очереди типа <code>CommandQueueBase</code> возвращает <code>CLTaskBase</code>.</li>
</ul>
<p>Если при выполнении возникла ошибка, о ней выведет не полную информацию. Чтобы получить всю информацию - используется <code>try</code>:</p>
<pre><code>try
//ToDo ваш код, вызывающий ошибку
except
// Writeln выводит все внутренние исключения. &quot;e.ToString&quot; тоже.
on e: Exception do Writeln(e);
end;
</code></pre>
<p>Для этого кода есть стандартный снипет. Чтобы активировать его - напишите <code>tryo</code> и нажмите Shift+Пробел.</p>
</div>
<script>on_start_folder("Выполнение очередей", document.getElementById("page-14"))</script>
<script>on_end_folder()</script>
<div id="page-15" page_name="" hidden=true>
<h1>Есть всего 10 базовых способов создать очередь:</h1>
<ol>
<li><p><a path="Из буфера или kernel'a"> Из буфера / kernel'а </a></p>
</li>
<li><p><a path="Из готового результата"> Из готового результата </a></p>
</li>
<li><p><a path="С кодом для CPU"> Из обычной подпрограммы </a></p>
</li>
<li><p><a path="Комбинируя другие очереди"> Из нескольких других очередей </a></p>
</li>
<li><p><a path="Из очереди + преобразователя"> Из очереди + преобразования результата </a></p>
</li>
<li><p><a path="Из повторения очереди"> Из повторения одной очереди </a></p>
</li>
<li><p><a path="Множественное использование очереди"> Несколько очередей с общей работой </a></p>
</li>
<li><p><a path="Особые .Add методы"> Особыми <code>.Add*</code> методами </a></p>
</li>
<li><p><a path="Из ожидания очередей"> Из ожидания других очередей </a></p>
</li>
<li><p><a path="Не создавая явно"> Не пытаясь </a></p>
</li>
</ol>
</div>
<script>on_start_folder("Создание очередей", document.getElementById("page-15"))</script>
<div id="page-16" page_name="Из буфера или kernel'a" hidden=true>
<p>Самый просто способ создать очередь — выбрать объект типа <code>Buffer</code> или <code>Kernel</code> и вызвать для него метод <code>.NewQueue</code>.</p>
<p>Полученная очередь будет иметь особый тип: <code>BufferCommandQueue</code>/<code>KernelCommandQueue</code> для буфера/kernel'а соответственно.<br />
К такой очереди можно добавлять команды, вызывая её методы, имена которых начинаются с <code>.Add...</code>.</p>
<p>К примеру:</p>
<pre><code>uses OpenCLABC;
begin
// Буфер достаточного размера чтоб содержать 3 значения типа integer
var b := new Buffer( 3*sizeof(integer) );
// Создаём очередь
var q := b.NewQueue;
// Добавлять команды в полученную очередь можно вызывая соответствующие методы
q.AddWriteValue(1, 0*sizeof(integer) );
// Методы, добавляющие команду в очередь - возвращают очередь, для которой их вызвали (не копию а ссылку на оригинал)
// Поэтому можно добавлять по несколько команд в 1 строчке:
q.AddWriteValue(5, 1*sizeof(integer) ).AddWriteValue(7, 2*sizeof(integer) );
// Все команды в q будут выполняться последовательно
Context.Default.SyncInvoke(q);
// Вообще чтение тоже надо делать через очереди, но для простого примера - и неявные очереди подходят
b.GetArray1&amp;&lt;integer&gt;(3).Println;
end.
</code></pre>
<p>Также, очереди <code>BufferCommandQueue</code>/<code>KernelCommandQueue</code> можно создавать из очередей, возвращающих <code>Buffer</code>/<code>Kernel</code> соответственно. Для этого используется конструктор:</p>
<pre><code>var q0: CommandQueue&lt;Buffer&gt;;
...
var q := new BufferCommandQueue(q0);
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-16"))</script>
<div id="page-17" page_name="Из готового результата" hidden=true>
<p>Переменной очереди можно присвоить значение, тип которого совпадает с возвращаемым значением очереди:</p>
<pre><code>var q: CommandQueue&lt;integer&gt; := 5;
</code></pre>
<p>Этот код присваевает переменной <code>q</code> константную очередь, которая ничего не выполняет и возвращает <code>5</code>.</p>
<p>Получить значение, из которого создали константную очередь, можно преобразовав её к <code>ConstQueue&lt;&gt;</code>:</p>
<pre><code>if q is ConstQueue&lt;integer&gt;(var cq) then
Writeln($'Очередь была создана из значения ({cq.Val})') else
Writeln($'Очередь не константная');
</code></pre>
<hr />
<p><code>CommandQueueBase</code> также можно создать из возвращаемого значения, но для этого тип значения должен быть <code>Object</code>:</p>
<pre><code>var q: CommandQueueBase := 5 as object;
</code></pre>
<p>Чтобы получить значение, из которого создали константную очередь, когда не знаете его тип - используйте интерфейс <code>IConstQueue</code>:</p>
<pre><code>if q is IConstQueue(var cq) then
Writeln($'Очередь была создана из значения ({cq.GetConstVal})') else
Writeln($'Очередь не константная');
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-17"))</script>
<div id="page-18" page_name="С кодом для CPU" hidden=true>
<p>Иногда между командами для GPU надо вставить выполнение обычного кода на CPU.
А разрывать для этого очередь на две части - плохо, потому что
одна целая очередь всегда выполнится быстрее двух её частей.</p>
<p>Для таких случае существуют глобальные подпрограммы <code>HFQ</code> и <code>HPQ</code>:</p>
<p>HFQ — Host Function Queue<br />
HPQ — Host Procedure Queue<br />
(Хост в контексте OpenCL - это CPU, потому что с него посылаются команды для GPU)</p>
<p>Они возвращают очередь, выполняющую код (функцию/процедуру соотвественно) на CPU.<br />
Пример применения приведён
<a path="../Возвращаемое значение очередей/">
на странице выше
</a>
.</p>
<hr />
<p>Если во время выполнения очереди возникает какая-либо ошибка - весь пользовательский код,
выполняемый на CPU в этой очереди получает <code>ThreadAbortException</code>:</p>
<pre><code>uses OpenCLABC;
begin
var t := Context.Default.BeginInvoke(
HPQ(()-&gt;
begin
try
Sleep(1000);
except
on e: Exception do lock output do
begin
Writeln('Ошибка во время выполнения первого HPQ:');
Writeln(e);
Writeln;
end;
end;
// Это никогда не выполнится, потому что
// ThreadAbortException кидает себя ещё раз в конце try
Writeln(1);
end)
*
HPQ(()-&gt;
begin
Sleep(500);
raise new Exception('abc');
end)
);
try
t.Wait;
except
on e: Exception do lock output do
begin
Writeln('Ошибка во время выполнения очереди:');
Writeln(e);
end;
end;
end.
</code></pre>
<p>Исключение <code>ThreadAbortException</code> во многом опасно.<br />
Подробнее можно прочитать в <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=netframework-4.8">справке от microsoft</a>.</p>
<p>А в кратце, если в вашем коде была кинута <code>ThreadAbortException</code> - становится очень сложно сказать что-либо о его состоянии.<br />
Даже потокобезопастный код, как вызов <code>Buffer.Dispose</code>, может привести к утечкам памяти, если посреди него кинут <code>ThreadAbortException</code>.</p>
<p>Считайте получение <code>ThreadAbortException</code> критической ошибкой, после которой очень желателен перезапуск всего .exe файла.</p>
</div>
<script>on_page_added(document.getElementById("page-18"))</script>
<div id="page-19" page_name="Комбинируя другие очереди" hidden=true>
<p>Если сложить две очереди A и B (<code>var C := A+B</code>) — получится очередь C, в которой сначала выполнится A, а затем B.<br />
Очередь C будет считаться выполненной тогда, когда выполнится очередь B.</p>
<p>Если умножить две очереди A и B (<code>var C := A*B</code>) — получится очередь C, в которой одновременно начнут выполняться A и B.<br />
Очередь C будет считаться выполненной тогда, когда обе очереди (A и B) выполнятся.</p>
<p>Как и в математике, умножение имеет бОльший приоритет чем сложение.</p>
<p>В обоих случаях очередь C будет возвращать то, что вернула очередь B. То есть если складывать и умножать много очередей - результат будет всегда возвращать то, что вернула самая последняя очередь.</p>
<p>Простейший пример:</p>
<pre><code>uses OpenCLABC;
begin
var q1 := HPQ(()-&gt;
begin
// lock необходим чтобы при параллельном выполнении два потока
// не пытались использовать вывод одновременно. Иначе выведет кашу
lock output do Writeln('Очередь 1 начала выполняться');
Sleep(500);
lock output do Writeln('Очередь 1 закончила выполняться');
end);
var q2 := HPQ(()-&gt;
begin
lock output do Writeln('Очередь 2 начала выполняться');
Sleep(500);
lock output do Writeln('Очередь 2 закончила выполняться');
end);
Writeln('Последовательное выполнение:');
Context.Default.SyncInvoke( q1 + q2 );
Writeln;
Writeln('Параллельное выполнение:');
Context.Default.SyncInvoke( q1 * q2 );
end.
</code></pre>
<p>Операторы += и *= также применимы к очередям.<br />
И как и для чисел - <code>A += B</code> работает как <code>A := A+B</code> (и аналогично с *=).<br />
А значит, возвращаемые типы очередей A и B должны быть одинаковыми, чтобы к ним можно было применить +=/*=.</p>
<p>Если надо сложить/умножить много очередей - лучше применять <code>CombineSyncQueue</code>/<code>CombineAsyncQueue</code> соответственно.<br />
Эти подпрограммы работают немного быстрее чем сложение и умножение, если объединять больше двух очередей.</p>
<p>Кроме того, они могут принимать ещё один параметр перед очередями:
Этот параметр позволяет указать функцию преобразования, которая использует результаты всех входных очередей:</p>
<pre><code>uses OpenCLABC;
begin
var q1 := HFQ( ()-&gt;1 );
var q2 := HFQ( ()-&gt;2 );
// Выводит 2, то есть только результат последней очереди
// Так сделано из за вопросов производительности
Context.Default.SyncInvoke( q1+q2 ).Println;
// Однако бывает так, что нужны результаты всех сложенных/умноженных очередей
// В таком случае надо использовать CombineSyncQueue и CombineAsyncQueue
// А точнее их перегрузку, первый параметр которой - функция преобразования
Context.Default.SyncInvoke(
CombineSyncQueue(
results-&gt;results.JoinToString, // Функция преобразования
q1, q2
)
).Println;
// Теперь выводит строку &quot;1 2&quot;. Это то же самое, что вернёт &quot;Arr(1,2).JoinToString&quot;
end.
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-19"))</script>
<div id="page-20" page_name="Из очереди + преобразователя" hidden=true>
<p>Если надо с минимальными затратами изменить представление компилятора об очереди - лучше всего использовать <code>.Cast</code>.<br />
Но он ограничен примерно так же, как метод последовательностей <code>.Cast</code>. То есть:</p>
<pre><code>uses OpenCLABC;
type t1 = class end;
type t2 = class(t1) end;
begin
var Q1: CommandQueue&lt;integer&gt; := 5;
var Q2: CommandQueueBase := Q1;
var Q3: CommandQueue&lt;t1&gt; := (new t2) as t1;
var Q4: CommandQueue&lt;t1&gt; := new t1;
var Q5: CommandQueue&lt;t2&gt; := new t2;
// Можно, потому что к object можно преобразовать всё
Context.Default.SyncInvoke( Q1.Cast&amp;&lt;object&gt; );
// Нельзя, преобразование между 2 записями, как из integer в byte - это сложный алгоритм
Context.Default.SyncInvoke( Q1.Cast&amp;&lt;byte&gt; );
// Можно, Q2 и так имеет тип CommandQueue&lt;integer&gt;, а значит тут Cast вернёт (Q2 as CommandQueue&lt;integer&gt;)
Context.Default.SyncInvoke( Q2.Cast&amp;&lt;integer&gt; );
// Можно, потому что Q3 возвращает t2
Context.Default.SyncInvoke( Q3.Cast&amp;&lt;t2&gt; );
// Нельзя, Q4 возвращает не t2 а t1, поэтому к t2 преобразовать не получится
Context.Default.SyncInvoke( Q4.Cast&amp;&lt;t2&gt; );
// Можно, потому что t2 наследует от t1
Context.Default.SyncInvoke( Q5.Cast&amp;&lt;t1&gt; );
end.
</code></pre>
<p>Ну а если эти ограничения не подходят - остаётся только <code>.ThenConvert</code>.
Он позволяет указать любой алгоритм преобразования, но работает медленнее:</p>
<pre><code>uses OpenCLABC;
begin
var q := HFQ(()-&gt;123);
Context.Default.SyncInvoke(
q.ThenConvert(i -&gt; i*2 )
).Println;
end.
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-20"))</script>
<div id="page-21" page_name="Из повторения очереди" hidden=true>
<p><del>В данный момент всё ещё не работает... Но уже совсем скоро, правда-правда!</del></p>
</div>
<script>on_page_added(document.getElementById("page-21"))</script>
<div id="page-22" page_name="Множественное использование очереди" hidden=true>
<p>Одну и ту же очередь можно использовать несколько раз, в том числе одновременно:</p>
<pre><code>uses OpenCLABC;
begin
var Q := HPQ(()-&gt;lock output do Writeln('Q выполнилась'));
var t1 := Context.Default.BeginInvoke(Q);
var t2 := Context.Default.BeginInvoke(Q*Q);
t1.Wait;
t2.Wait;
end.
</code></pre>
<p>Но эта программа выведет &quot;Q выполнилась&quot; три раза, потому что при каждом упоминании - Q запускается ещё раз.</p>
<p>Это не всегда хорошо. К примеру, может быть так что <code>Q</code> содержит какой то затратный алгоритм. Или ввод значения с клавиатуры:</p>
<pre><code>uses OpenCLABC;
begin
var Q := HFQ(()-&gt;
begin
lock input do
Result := ReadInteger;
end);
Context.Default.SyncInvoke(CombineAsyncQueue(
res-&gt;res,
Q,
Q.ThenConvert(i-&gt;i*i),
Q.ThenConvert(i-&gt;i*i*i)
)).Println;
end.
</code></pre>
<p>Эта программа запросит три разных значения, что не всегда то что надо.</p>
<p>Чтоб использовать результат одной очереди несколько раз - используется <code>.Multiusable</code>:</p>
<pre><code>uses OpenCLABC;
begin
var Q := HFQ(()-&gt;
begin
lock input do
Result := ReadInteger;
end);
var Qs := Q.Multiusable;
Context.Default.SyncInvoke(CombineAsyncQueue(
res-&gt;res,
Qs(),
Qs().ThenConvert(i-&gt;i*i),
Qs().ThenConvert(i-&gt;i*i*i)
)).Println;
end.
</code></pre>
<p><code>.Multiusable</code> создаёт новую функцию, вызывая которую можно получить любое количество очередей,
у которых будет общий результат.</p>
<p>Каждый вызов <code>.Multiusable</code> создаёт именно новую функцию.<br />
Это значит, что если использовать результаты двух вызовов <code>.Multiusable</code> - исходная очередь выполнится два раза.</p>
<p><code>.Multiusable</code> не работает между вызовами <code>Context.BeginInvoke</code>:</p>
<pre><code>uses OpenCLABC;
begin
var Q := HFQ(()-&gt;
begin
lock input do
Result := ReadInteger;
end);
var Qs := Q.Multiusable;
Context.Default.SyncInvoke( Qs() ).Println;
Context.Default.SyncInvoke( Qs().ThenConvert(i-&gt;i*i) ).Println;
end.
</code></pre>
<p>Эта программа запросит ввод два раза.</p>
<p>Если контекст у двух очередей общий - лучше объединить вызовы <code>Context.BeginInvoke</code>.
Так не только <code>.Multiusable</code> будет работать, но и выполнение будет в целом быстрее.</p>
<p>А если контекст разный - надо сохранять результат в переменную и использовать <code>Wait</code> очереди
(<a path="Из ожидания очередей">подробнее на странице ниже</a>).</p>
</div>
<script>on_page_added(document.getElementById("page-22"))</script>
<div id="page-23" page_name="Особые .Add методы" hidden=true>
<p>Между командами для GPU (хранимыми в очередях типов <code>BufferCommandQueue</code> и <code>KernelCommandQueue</code>)
бывает надо вставить выполнение другой очереди или кода для CPU.</p>
<p>Это можно сделать, используя несколько <code>.NewQueue</code>:</p>
<pre><code>var b: Buffer;
var q0: CommandQueueBase;
...
var q :=
b.NewQueue.AddWriteValue(...) +
q0 +
HPQ(...) +
b.NewQueue.AddWriteValue(...)
;
</code></pre>
<p>Однако можно сделать и красивее:</p>
<pre><code>var b: Buffer;
var q0: CommandQueueBase;
...
var q := b.NewQueue
.AddWriteValue(...)
.AddQueue(q0)
.AddProc(...)
.AddWriteValue(...)
;
</code></pre>
<p>(вообще callback AddProc'а принимает исходный буфер/kernel параметром, что делает его более похожим на <code>.ThenConvert</code>, чем <code>HPQ</code>)</p>
<p>Эти методы не имеют незаменимых применений, но позволяют сделать код значительно читабельнее.</p>
<p>Кроме того, будьте осторожны, защита от дурака для такого случая - отсутствует:</p>
<pre><code>var q := b.NewQueue;
q.AddQueue(q);
</code></pre>
<p>Все <code>.Add*</code> методы добавляют команды в существующую очередь, а не создают новую.
Поэтому очередь, созданная предыдущим кодом при попытке выполнения начнёт циклически запускать саму себя.</p>
</div>
<script>on_page_added(document.getElementById("page-23"))</script>
<div id="page-24" page_name="Из ожидания очередей" hidden=true>
<p>Есть всего 3 группы подпрограмм, создающих очереди для ожидания других очередей:</p>
<ol>
<li><p>Глобальные, <code>WaitFor*</code>:<br />
Ничего не делают сами, но блокируют выполнение пока указанные очереди не выполнятся.</p>
</li>
<li><p>Особые методы <code>.Add*</code> - <code>.AddWait*</code>:<br />
Как и <code>.AddQueue</code> и <code>.AddProc</code>, <code>.AddWait(...)</code> это всего лишь аналог
<code>.AddQueue(WaitFor(...))</code>, существующий только ради простоты кода.</p>
</li>
<li><p>Методы очередей, <code>.ThenWaitFor*</code>:<br />
Так же не имеет незаменимых применений, но ещё полезнее.<br />
<code>Q.ThenWaitFor(...)</code> работает как <code>Q + WaitFor(...)</code>, за исключением того, что возвращает результат <code>Q</code>.<br />
То есть, правильнее будет:</p>
<pre><code>var Qs := Q.Multiusable;
Result := Qs() + WaitFor(...) + Qs();
</code></pre>
<p>Но это уже даже в одну строчку не напишешь.</p>
</li>
</ol>
<p>В каждой из групп Wait очередей - <code>*</code> можно заменить:</p>
<ol>
<li>Ничем: Ожидание одной очереди.</li>
<li>На <code>All</code>: Ожидание всех указанных очередей.</li>
<li>На <code>Any</code>: Ожидание любой из указанных очередей (какая раньше выполнится).</li>
</ol>
<p>То есть, к примеру, <code>WaitForAny</code> создаёт очередь, которая сама ничего не выполняет, но ожидает выполнения любой из указанных очередей.</p>
<hr />
<p><code>Wait</code> очереди делают реализацию некоторых сложных деревьев выполнения очередей возможными.<br />
Примеры можно найти в папке <code>PABCWork.NET\Samples\OpenCLABC\Wait очереди</code>.</p>
<p>Но они наиболее полезны благодаря одному особенному свойству:<br />
<code>Wait</code> очереди работают даже между вызовами <code>Context.BeginInvoke</code>, в отличии от всего остального в <code>OpenCLABC</code>.</p>
<p>Это не всегда безопастно:</p>
<pre><code>Context.Default.BeginInvoke(Q1);
Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
</code></pre>
<p>Проблема этого кода в том, что <code>Q1</code> может закончить выполняться ещё до того как <code>WaitFor(Q1)</code> начнёт ожидать.</p>
<p>Чтоб такое не происходило - надо всегда запускать ожидающую очередь раньше ожидаемой:</p>
<pre><code>Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
Context.Default.BeginInvoke(Q1);
</code></pre>
<p>Но, как всегда, лучше, по возможности, объединять вызовы <code>Context.BeginInvoke</code>:</p>
<pre><code>Context.Default.BeginInvoke(
( Q1 ) *
( WaitFor(Q1) + Q2 )
);
</code></pre>
<p>Все ожидающие очереди начинают ожидать в самом начале вызова <code>Context.BeginInvoke</code>, перед тем как очередь начнёт выполнятся.
Поэтому если ожидающая и ожидаемая очереди находятся в общем <code>Context.BeginInvoke</code> - о их порядке можно не волноваться.</p>
<hr />
<h3>Множественное ожидание несколько раз выполнившейся очереди</h3>
<p>Когда начинает выполняться <code>Context.BeginInvoke</code> - для каждой ожидающей очереди в соответствующей
ожидаемой очереди создаётся новый счётчик для того, сколько раз ожидаемая очередь была выполнена.</p>
<p>Поэтому можно делать так:</p>
<pre><code>uses OpenCLABC;
begin
var Q1 := HPQ(()-&gt;
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
end);
var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));
var t1 := Context.Default.BeginInvoke(
(WaitFor(Q1)+Q2) *
(WaitFor(Q1)+Q3)
);
Context.Default.SyncInvoke(Q1+Q1);
t1.Wait; // Чтобы вывести ошибки, если возникнут при выполнении
end.
</code></pre>
<p>Ну и, конечно, лучше совместить вызовы <code>Context.BeginInvoke</code>, раз контекст общий:</p>
<pre><code>uses OpenCLABC;
begin
var Q1 := HPQ(()-&gt;
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
end);
var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));
Context.Default.SyncInvoke(
(Q1+Q1) *
(WaitFor(Q1)+Q2) *
(WaitFor(Q1)+Q3)
);
end.
</code></pre>
<p>Каждое окончание выполнения <code>Q1</code> добавляет 1 в счётчик внутри <code>Q1</code>.<br />
Каждое окончание ожидания <code>WaitFor(Q1)</code> отнимание 1 от того же счётчика.</p>
<p>Будьте осторожны, лишняя <code>Wait</code> очередь вызовет зависание:</p>
<pre><code>uses OpenCLABC;
begin
var Q1 := HFQ(()-&gt;0);
var t1 := Context.Default.BeginInvoke(
WaitFor(Q1) +
WaitFor(Q1) // второй запуск Q1 никогда не произойдёт, поэтому это зависнет
);
Context.Default.SyncInvoke(Q1);
t1.Wait;
end.
</code></pre>
<hr />
<p>Cчётчик создаётся для каждой пары [ожидаемой очереди] и [вызова <code>Context.BeginInvoke</code> с ожидающей очередью].
То есть это тоже сработает:</p>
<pre><code>uses OpenCLABC;
begin
var Q1 := HPQ(()-&gt;
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
end);
var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));
var Q4 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q4'));
var Q5 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q5'));
var t1 := Context.Default.BeginInvoke(
( WaitFor(Q1)+Q2 ) *
( WaitFor(Q1)+Q3 )
);
var t2 := Context.Default.BeginInvoke(
( WaitFor(Q1)+Q4 ) *
( WaitFor(Q1)+Q5 )
);
// Каждый вызов Q1 тут - активирует по 1 WaitFor(Q1) в каждом CLTask
Context.Default.SyncInvoke(Q1+Q1);
t1.Wait;
t2.Wait;
end.
</code></pre>
<p>Но каждый <code>Context.BeginInvoke</code> для ожидаемой очереди - добавляет в уже существующий счётчик:</p>
<pre><code>uses OpenCLABC;
begin
var Q1 := HPQ(()-&gt;
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
end);
var Q2 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q3'));
var Q4 := HPQ(()-&gt;lock output do Writeln('Выполнилась Q4'));
var t1 := Context.Default.BeginInvoke(
( WaitFor(Q1)+Q3 ) *
( WaitFor(Q1)+Q4 )
);
var t2 := Context.Default.BeginInvoke(WaitFor(Q1)+Q2+Q1);
Context.Default.SyncInvoke(Q1);
t1.Wait;
t2.Wait;
end.
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-24"))</script>
<div id="page-25" page_name="Не создавая явно" hidden=true>
<p>Передавать команды по одной, когда их несколько - ужасно не эффективно!<br />
Но нередко бывает так, что команда всего одна. Или для отладки надо одноразово выполнить одну команду.</p>
<p>Для таких случаев можно создавать очередь неявно:<br />
У каждого <code>.Add*</code> метода есть дублирующий метод в оригинальном объекте. Такие методы сами создают
новую очередь, добавляют в неё одну соответствующую команду и выполняют полученную очередь в <code>Context.Default.SyncInvoke(...)</code>.</p>
<p>Обычный код с очередями:</p>
<pre><code>uses OpenCLABC;
begin
var b := new Buffer( 3*sizeof(integer) );
var Q_BuffWrite := b.NewQueue
.AddWriteValue(1, 0*sizeof(integer) )
.AddWriteValue(5, 1*sizeof(integer) )
.AddWriteValue(7, 2*sizeof(integer) )
;
var Q_BuffRead :=
b.NewQueue.AddGetArray1&amp;&lt;integer&gt;
.ThenConvert(A-&gt;A.Println);
;
Context.Default.SyncInvoke(
Q_BuffWrite +
Q_BuffRead
);
end.
</code></pre>
<p>Он же, но с неявными очередями:</p>
<pre><code>uses OpenCLABC;
begin
var b := new Buffer( 3*sizeof(integer) );
// Аналог Q_BuffWrite
b.WriteValue(1, 0*sizeof(integer) );
b.WriteValue(5, 1*sizeof(integer) );
b.WriteValue(7, 2*sizeof(integer) );
// Аналог Q_BuffRead
b.GetArray1&amp;&lt;integer&gt;.Println;
end.
</code></pre>
</div>
<script>on_page_added(document.getElementById("page-25"))</script>
<script>on_end_folder()</script>
<div id="page-26" page_name="" hidden=true>
<p>Все методы создающие одну команду (<code>.Add*</code> методы и все методы неявных очередей)
могут принимать очередь вместо значения в качестве любого параметра. Но в таком случае
возвращаемый тип очереди должен совпадать с типом параметра. К примеру:</p>
<pre><code>uses OpenCLABC;
begin
var b := new Buffer(10*sizeof(integer));
// Очищаем весь буфер ноликами, чтобы не было мусора
b.FillValue(0);
var q := b.NewQueue
// Второй параметр AddWriteValue - отступ от начала буфера
// Он имеет тип integer, а значит можно передать и CommandQueue&lt;integer&gt;
// Таким образом, в параметр сохраняется алгоритм, а не готовое значение
// Поэтому 3 вызова ниже могут получится с 3 разными отступами
.AddWriteValue(5, HFQ(()-&gt; Random(0,9)*sizeof(integer) ))
as CommandQueue&lt;Buffer&gt;;
Context.Default.SyncInvoke(q);
Context.Default.SyncInvoke(q);
Context.Default.SyncInvoke(q);
b.GetArray1&amp;&lt;integer&gt;.Println;
end.
</code></pre>
<p>Все вложенные очереди начинают выполняться сразу при вызове метода <code>Context.BeginInvoke</code>, не ожидая других очередей.</p>
<p>Обычно вложенные очереди используются при вызове kernel'а,
когда надо записать что-то в буфер прямо перед вызовом kernel'а.</p>
</div>
<script>on_start_folder("Вложенные очереди", document.getElementById("page-26"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
<div id="page-27" page_name="" hidden=true>
<p>В данной справке в нескольких местах можно встретить утверждения вроде</p>
<blockquote>
<p>Вызовы <code>Context.BeginInvoke</code> стоит, по возможности, объединять.</p>
</blockquote>
<p>Данный раздел подпробнее объясняет устройство модуля, что делает подобные утверждения более понятными.</p>
<p>Читать его не необходимо для написания работающего кода, но желательно для написания качественного кода.</p>
<p>Но стоит сказать заранее - это не полное объяснение внутренностей модуля. Объясняется только то, что скорее всего окажется нужным.
Если хотите ещё более полное понимание - используйте Ctrl+клик в IDE по именам, чтоб смотреть исходный код.</p>
<hr />
<h1>Страницы:</h1>
<ul>
<li><a path="Внутренности BeginInvoke"> Что делает вызов <code>BeginInvoke</code> </a></li>
</ul>
</div>
<script>on_start_folder("Оптимизация", document.getElementById("page-27"))</script>
<div id="page-28" page_name="Внутренности BeginInvoke" hidden=true>
<p>Кроме самого выполнения очередей - им так же необходима инициализация и финализация.</p>
<hr />
<h3>Инициализация</h3>
<p>Для начала, перед тем как любая из под-очередей в <code>BeginInvoke</code> начнёт выполняться - необходимо инициалировать <code>Wait</code> очереди.
Иначе у ожидаемой очереди всегда будет шанс выполнится до того как ожидающая начнёт ожидать.</p>
<p>Инициализация <code>Wait</code> очередей заключается в обходе всего дерева под-очередей. Для каждой <code>Wait</code> очереди, ожидающей уникальную очередь,
создаётся пара ключ-значения (<code>CLTask</code> и счётчик для него) и добавляется обработчик <code>CLTask.WhenDone</code> с удалением этой пары.</p>
<hr />
<h3>Запуск очередей</h3>
<p>Тоже заключается в обходе дерева под-очередей.</p>
<p>Но в этот раз очереди по которым прошлись - уже начали выполнятся, не ожидая окончания обхода всего дерева.</p>
<p>Как только этот обход закончен - метод <code>BeginInvoke</code> возвращает свой <code>CLTask</code> коду, вызвавшему его.</p>
<p>Единственное исключение - если очередь, каким то образом, уже завершила выполняться, к моменту как обход дерева закончился.<br />
Такое возможно, к примеру, если все под-очереди - константные. В таком случае финализация выполняется до выхода из <code>BeginInvoke</code>.</p>
<hr />
<h3>Финализация</h3>
<p>В основном заключается в вызове обработчиков события, как <code>CLTask.WhenDone</code>.
Обработчики созданные <code>Wait</code> очередями ничем не особенны, поэтому они тоже выполняются тут.</p>
<hr />
<p>Основное преимущество объединения вызовов <code>BeginInvoke</code> состоит в различии следующих 2 случаев:</p>
<pre><code>Context.Default.SyncInvoke(A+B);
</code></pre>
<pre><code>Context.Default.SyncInvoke(A);
Context.Default.SyncInvoke(B);
</code></pre>
<p>В первом случае пока выполнится <code>A</code> - <code>B</code> уже, скорее всего, окажется полностью запущено.
А значит как только <code>A</code> закончит выполнятся - ход выполнения перейдёт на <code>B</code>.</p>
<p>А во втором случае - между окончанием выполнения <code>A</code> и запуском <code>B</code> - будет произведено множество проверок,
а так же выходов/входов в объёмные (что значит JIT их не инлайнит) подпрограммы, как конструктор <code>CLTask</code>.</p>
<p>Да, всё это мелочи. Но нулевая задержка всегда лучше ненулевой.</p>
<hr />
<p>Ну а когда всё же приходится вызывать 2 отдельных <code>BeginInvoke</code>, к примеру на 2 разных
контекстах - можно использовать <code>Wait</code> очереди, чтоб добится того же эффекта:</p>
<pre><code>c2.BeginInvoke(WaitFor(A)+B);
c1.BeginInvoke(A);
</code></pre>
<p>Внутренние оптимизации <code>OpenCLABC</code> делают этот код практически не отличимым по скорости, от <code>BeginInvoke(A+B)</code>.</p>
<p>Единственное различие - время инициализации. Потому что <code>A</code> не запустится, пока не закончится обход дерева <code>c2.BeginInvoke</code>.</p>
</div>
<script>on_page_added(document.getElementById("page-28"))</script>
<script>on_end_folder()</script>
<script>on_end_folder()</script>
</body>
</html>